mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Enhance audit documentation and improve Ref property handling
- Added a detailed section in the audit documentation outlining the sync system layout for both CLI syncback and two-way sync pipelines, including file mappings and processing steps. - Updated `init.meta.json5` to include the `Rojo_Ref_PrimaryPart` attribute for better reference tracking. - Improved the `ChangeProcessor` to populate the `RefPathIndex` from existing meta/model files at startup, ensuring accurate handling of pre-existing `Rojo_Ref_*` attributes during renames. - Refactored the `remove_file` method in `RefPathIndex` to `remove_all_for_file`, enhancing the re-indexing process after attribute removals. - Added tests to verify the correct behavior of the `RefPathIndex` and ensure robustness against edge cases. These changes improve the clarity of the audit process and enhance the reliability of reference path management in the synchronization system.
This commit is contained in:
parent
c3efb0e560
commit
e617e8fcda
9 changed files with 638 additions and 54 deletions
|
|
@ -101,6 +101,11 @@ impl ChangeProcessor {
|
|||
.spawn(move || {
|
||||
log::trace!("ChangeProcessor thread started");
|
||||
|
||||
// Populate the RefPathIndex from existing meta/model files
|
||||
// so that renames of instances with pre-existing Rojo_Ref_*
|
||||
// attributes are handled correctly from the first rename.
|
||||
task.populate_initial_ref_path_index();
|
||||
|
||||
// Tracks when to run the next reconciliation pass. Set to
|
||||
// Some(future_instant) after VFS events arrive, cleared
|
||||
// after reconcile_tree() runs. This ensures we only do the
|
||||
|
|
@ -241,6 +246,61 @@ impl JobThreadContext {
|
|||
rel_path(path, &self.project_root)
|
||||
}
|
||||
|
||||
/// Scan all `.meta.json5`, `.model.json5`, `.meta.json`, `.model.json`
|
||||
/// files under the project root for existing `Rojo_Ref_*` attributes
|
||||
/// and populate the `RefPathIndex`. This ensures pre-existing ref
|
||||
/// attributes (from CLI syncback, manual edits, or previous sessions)
|
||||
/// are indexed so rename path updates work from the first rename.
|
||||
fn populate_initial_ref_path_index(&self) {
|
||||
let mut count = 0;
|
||||
let mut dirs_to_visit = vec![self.project_root.clone()];
|
||||
while let Some(dir) = dirs_to_visit.pop() {
|
||||
let entries = match fs::read_dir(&dir) {
|
||||
Ok(entries) => entries,
|
||||
Err(_) => continue,
|
||||
};
|
||||
for entry in entries.filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
dirs_to_visit.push(path);
|
||||
continue;
|
||||
}
|
||||
let name = match path.file_name().and_then(|n| n.to_str()) {
|
||||
Some(n) => n,
|
||||
None => continue,
|
||||
};
|
||||
if !(name.ends_with(".meta.json5")
|
||||
|| name.ends_with(".model.json5")
|
||||
|| name.ends_with(".meta.json")
|
||||
|| name.ends_with(".model.json"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Ok(bytes) = fs::read(&path) {
|
||||
if let Ok(val) = crate::json::from_slice::<serde_json::Value>(&bytes) {
|
||||
if let Some(attrs) = val.get("attributes").and_then(|a| a.as_object()) {
|
||||
let mut index = self.ref_path_index.lock().unwrap();
|
||||
for (key, value) in attrs {
|
||||
if key.starts_with(crate::REF_PATH_ATTRIBUTE_PREFIX) {
|
||||
if let Some(path_str) = value.as_str() {
|
||||
index.add(path_str, &path);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if count > 0 {
|
||||
log::info!(
|
||||
"RefPathIndex: populated {} Rojo_Ref_* entries from existing meta/model files",
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the init file inside a directory-format script folder.
|
||||
/// Returns the path to the first `init.*.luau` or `init.*.lua` found.
|
||||
fn find_init_file(dir: &Path) -> Option<PathBuf> {
|
||||
|
|
@ -403,12 +463,7 @@ impl JobThreadContext {
|
|||
/// Uses the `RefPathIndex` for O(affected_files) lookup instead of
|
||||
/// scanning the full tree. After updating files, also updates the index
|
||||
/// keys and filesystem paths so future renames remain efficient.
|
||||
fn update_ref_paths_after_rename(
|
||||
&self,
|
||||
_tree: &mut crate::snapshot::RojoTree,
|
||||
old_path: &str,
|
||||
new_path: &str,
|
||||
) {
|
||||
fn update_ref_paths_after_rename(&self, old_path: &str, new_path: &str) {
|
||||
if old_path == new_path {
|
||||
return;
|
||||
}
|
||||
|
|
@ -458,7 +513,7 @@ impl JobThreadContext {
|
|||
if let std::path::Component::Normal(os_str) = comp {
|
||||
if let Some(s) = os_str.to_str() {
|
||||
if s == old_segment {
|
||||
result.push(new_segment);
|
||||
result.push(slugified_new());
|
||||
replaced = true;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1535,11 +1590,7 @@ impl JobThreadContext {
|
|||
crate::escape_ref_path_segment(new_name).into_owned()
|
||||
};
|
||||
if *old_ref_path != new_ref_path {
|
||||
self.update_ref_paths_after_rename(
|
||||
&mut tree,
|
||||
old_ref_path,
|
||||
&new_ref_path,
|
||||
);
|
||||
self.update_ref_paths_after_rename(old_ref_path, &new_ref_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,9 +212,9 @@ impl RefPathIndex {
|
|||
}
|
||||
|
||||
/// Remove `meta_file` from ALL entries in the index.
|
||||
/// Used when a Rojo_Ref_* attribute is removed and we don't know the old
|
||||
/// path value.
|
||||
pub fn remove_file(&mut self, meta_file: &Path, _attr_name: &str) {
|
||||
/// Used when re-indexing a file: first remove all old entries, then
|
||||
/// re-add entries for the attributes that remain.
|
||||
pub fn remove_all_for_file(&mut self, meta_file: &Path) {
|
||||
let mut empty_keys = Vec::new();
|
||||
for (path, files) in &mut self.paths_to_files {
|
||||
files.remove(meta_file);
|
||||
|
|
@ -516,17 +516,39 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn ref_path_index_remove_file() {
|
||||
fn ref_path_index_remove_all_for_file() {
|
||||
let mut index = RefPathIndex::new();
|
||||
let file = Path::new("/a.meta.json5");
|
||||
index.add("Workspace/Part1", file);
|
||||
index.add("Workspace/Part2", file);
|
||||
|
||||
index.remove_file(file, "Rojo_Ref_PrimaryPart");
|
||||
index.remove_all_for_file(file);
|
||||
assert!(index.find_by_prefix("Workspace/Part1").is_empty());
|
||||
assert!(index.find_by_prefix("Workspace/Part2").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_path_index_remove_all_for_file_preserves_other_files() {
|
||||
let mut index = RefPathIndex::new();
|
||||
let file_a = Path::new("/a.meta.json5");
|
||||
let file_b = Path::new("/b.meta.json5");
|
||||
// Both files share the same ref path entry
|
||||
index.add("Workspace/Part1", file_a);
|
||||
index.add("Workspace/Part1", file_b);
|
||||
// file_a also has another ref
|
||||
index.add("Workspace/Part2", file_a);
|
||||
|
||||
index.remove_all_for_file(file_a);
|
||||
|
||||
// file_b's entry should be preserved
|
||||
let results = index.find_by_prefix("Workspace/Part1");
|
||||
assert_eq!(results.len(), 1);
|
||||
assert_eq!(results[0], PathBuf::from("/b.meta.json5"));
|
||||
|
||||
// file_a's other entry should be gone
|
||||
assert!(index.find_by_prefix("Workspace/Part2").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_path_index_update_prefix() {
|
||||
let mut index = RefPathIndex::new();
|
||||
|
|
|
|||
|
|
@ -107,8 +107,8 @@ pub fn update_ref_paths_in_file(
|
|||
return Ok(false);
|
||||
}
|
||||
|
||||
let bytes = fs::read(file_path)
|
||||
.with_context(|| format!("Failed to read {}", file_path.display()))?;
|
||||
let bytes =
|
||||
fs::read(file_path).with_context(|| format!("Failed to read {}", file_path.display()))?;
|
||||
let mut val: serde_json::Value = crate::json::from_slice(&bytes)
|
||||
.with_context(|| format!("Failed to parse JSON5 in {}", file_path.display()))?;
|
||||
if !val.is_object() {
|
||||
|
|
|
|||
|
|
@ -2748,17 +2748,6 @@ impl ApiService {
|
|||
let (properties, mut attributes) =
|
||||
self.filter_properties_for_meta(class_name.as_str(), &props, skip_prop);
|
||||
|
||||
// Collect Rojo_Ref_* path values for index maintenance.
|
||||
let ref_path_values: Vec<String> = ref_attributes
|
||||
.iter()
|
||||
.filter_map(|(_, v)| v.as_str().map(|s| s.to_string()))
|
||||
.collect();
|
||||
let removed_ref_values: Vec<String> = remove_attributes
|
||||
.iter()
|
||||
.filter(|a| a.starts_with(crate::REF_PATH_ATTRIBUTE_PREFIX))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// Merge Rojo_Ref_* attributes into the attributes map
|
||||
for (k, v) in ref_attributes {
|
||||
attributes.insert(k, v);
|
||||
|
|
@ -2884,22 +2873,27 @@ impl ApiService {
|
|||
}
|
||||
}
|
||||
|
||||
// Update the RefPathIndex with the new/removed Rojo_Ref_* entries.
|
||||
// Re-index the written meta file: remove all old entries for this
|
||||
// file, then add entries for all remaining Rojo_Ref_* attributes.
|
||||
// This is simpler and more correct than tracking individual
|
||||
// additions/removals (which can miss remaining attrs when one is
|
||||
// removed from a file that has multiple).
|
||||
{
|
||||
let mut index = self.ref_path_index.lock().unwrap();
|
||||
index.remove_all_for_file(&written_meta_path);
|
||||
|
||||
// Remove entries for attributes that were deleted
|
||||
for attr_name in &removed_ref_values {
|
||||
// We don't know the old path value here, but we can read
|
||||
// it from the file before removal. Since merge_or_build_meta
|
||||
// already removed them, we need to remove ALL entries that
|
||||
// point to this meta file. Scan the index for this file.
|
||||
index.remove_file(&written_meta_path, attr_name);
|
||||
}
|
||||
|
||||
// Add entries for new/updated Rojo_Ref_* attributes
|
||||
for path_value in &ref_path_values {
|
||||
index.add(path_value, &written_meta_path);
|
||||
if let Ok(bytes) = std::fs::read(&written_meta_path) {
|
||||
if let Ok(val) = crate::json::from_slice::<serde_json::Value>(&bytes) {
|
||||
if let Some(attrs) = val.get("attributes").and_then(|a| a.as_object()) {
|
||||
for (key, value) in attrs {
|
||||
if key.starts_with(crate::REF_PATH_ATTRIBUTE_PREFIX) {
|
||||
if let Some(path_str) = value.as_str() {
|
||||
index.add(path_str, &written_meta_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue