diff --git a/src/snapshot_middleware/dir.rs b/src/snapshot_middleware/dir.rs index b3af1cb0..2fe2c66a 100644 --- a/src/snapshot_middleware/dir.rs +++ b/src/snapshot_middleware/dir.rs @@ -161,6 +161,17 @@ pub fn syncback_dir_no_meta<'sync>( for new_child_ref in new_inst.children() { let new_child = snapshot.get_new_instance(*new_child_ref).unwrap(); + // Skip instances of ignored classes + if snapshot.should_ignore_class(&new_child.class) { + // Also remove from old_child_map so it won't be marked as removed + old_child_map.remove(new_child.name.as_str()); + log::debug!( + "Skipping instance {} because its class {} is ignored", + new_child.name, + new_child.class + ); + continue; + } if let Some(old_child) = old_child_map.remove(new_child.name.as_str()) { if old_child.metadata().relevant_paths.is_empty() { log::debug!( @@ -186,10 +197,32 @@ pub fn syncback_dir_no_meta<'sync>( } } // Any children that are in the old dom but not the new one are removed. - removed_children.extend(old_child_map.into_values()); + // Filter out instances of ignored classes from removal. + removed_children.extend(old_child_map.into_values().filter(|inst| { + if snapshot.should_ignore_class(inst.class_name().as_str()) { + log::debug!( + "Not removing instance {} because its class {} is ignored", + inst.name(), + inst.class_name() + ); + false + } else { + true + } + })); } else { // There is no old instance. Just add every child. for new_child_ref in new_inst.children() { + let new_child = snapshot.get_new_instance(*new_child_ref).unwrap(); + // Skip instances of ignored classes + if snapshot.should_ignore_class(&new_child.class) { + log::debug!( + "Skipping instance {} because its class {} is ignored", + new_child.name, + new_child.class + ); + continue; + } children.push(snapshot.with_joined_path(*new_child_ref, None)?); } } diff --git a/src/snapshot_middleware/project.rs b/src/snapshot_middleware/project.rs index 9540d764..8e9735e5 100644 --- a/src/snapshot_middleware/project.rs +++ b/src/snapshot_middleware/project.rs @@ -474,6 +474,18 @@ pub fn syncback_project<'sync>( // All of the children in this loop are by their nature not in the // project, so we just need to run syncback on them. for (name, new_child) in new_child_map.drain() { + // Skip instances of ignored classes + if snapshot.should_ignore_class(&new_child.class) { + // Also remove from old_child_map so it won't be marked as removed + old_child_map.remove(name.as_str()); + log::debug!( + "Skipping instance {} because its class {} is ignored", + new_child.name, + new_child.class + ); + continue; + } + let parent_path = match ref_to_path_map.get(&new_child.parent()) { Some(path) => path.clone(), None => { @@ -502,7 +514,19 @@ pub fn syncback_project<'sync>( } } } - removed_descendants.extend(old_child_map.drain().map(|(_, v)| v)); + // Filter out instances of ignored classes from removal + removed_descendants.extend(old_child_map.drain().filter_map(|(_, inst)| { + if snapshot.should_ignore_class(inst.class_name().as_str()) { + log::debug!( + "Not removing instance {} because its class {} is ignored", + inst.name(), + inst.class_name() + ); + None + } else { + Some(inst) + } + })); node_changed_map.push((&node.properties, &node.attributes, old_inst)) } let mut fs_snapshot = FsSnapshot::new(); diff --git a/src/syncback/mod.rs b/src/syncback/mod.rs index 61d26674..4ad9d504 100644 --- a/src/syncback/mod.rs +++ b/src/syncback/mod.rs @@ -385,6 +385,10 @@ pub struct SyncbackRules { /// syncback. #[serde(default, skip_serializing_if = "IndexMap::is_empty")] ignore_properties: IndexMap>, + /// A list of class names to ignore entirely during syncback. + /// Instances of these classes will not be added, removed, or synced. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + ignore_classes: Vec, /// Whether or not the `CurrentCamera` of `Workspace` is included in the /// syncback or not. Defaults to `false`. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/syncback/snapshot.rs b/src/syncback/snapshot.rs index 384033d6..2a2f252d 100644 --- a/src/syncback/snapshot.rs +++ b/src/syncback/snapshot.rs @@ -206,6 +206,24 @@ impl<'sync> SyncbackSnapshot<'sync> { .as_ref() .map(|rules| rules.ignore_trees.as_slice()) } + + /// Returns user-specified ignore classes. + #[inline] + pub fn ignore_classes(&self) -> Option<&[String]> { + self.data + .project + .syncback_rules + .as_ref() + .map(|rules| rules.ignore_classes.as_slice()) + } + + /// Checks if a class name should be ignored during syncback. + #[inline] + pub fn should_ignore_class(&self, class_name: &str) -> bool { + self.ignore_classes() + .map(|classes| classes.iter().any(|c| c == class_name)) + .unwrap_or(false) + } } pub fn filter_out_property(inst: &Instance, prop_name: &str) -> bool {