Refactor snapshot attribute handling in patch computation

- Removed redundant Rojo reference attributes from snapshot comparisons to prevent spurious diffs during VFS events.
- Updated the logic for interned instance additions to ensure deterministic ordering by redacted parent and name.

These changes aim to improve the accuracy of snapshot comparisons and enhance the handling of interned instances.
This commit is contained in:
Joe 2026-02-19 07:33:32 -06:00
parent c47e568cd4
commit eb114ac76e
7 changed files with 40 additions and 40 deletions

View file

@ -12,10 +12,6 @@ body:
changedMetadata: ~ changedMetadata: ~
changedName: ~ changedName: ~
changedProperties: changedProperties:
Attributes:
"!Attributes":
Rojo_Ref_PrimaryPart:
"!String": "@self/OtherPart"
PrimaryPart: PrimaryPart:
"!Ref": id-4 "!Ref": id-4
id: id-3 id: id-3

View file

@ -12,12 +12,6 @@ body:
changedMetadata: ~ changedMetadata: ~
changedName: ~ changedName: ~
changedProperties: changedProperties:
Attributes:
"!Attributes":
Rojo_Ref_CustomRef:
"!String": "@self/OtherPart"
Rojo_Ref_PrimaryPart:
"!String": "@self/Target"
CustomRef: CustomRef:
"!Ref": id-4 "!Ref": id-4
id: id-3 id: id-3

View file

@ -20,15 +20,6 @@ body:
Value: Value:
"!Ref": id-5 "!Ref": id-5
removed: [] removed: []
updated: updated: []
- changedClassName: ~
changedMetadata: ~
changedName: ~
changedProperties:
Attributes:
"!Attributes":
Rojo_Ref_PrimaryPart:
"!String": "@self/Target"
id: id-3
packetType: messages packetType: messages
sessionId: id-1 sessionId: id-1

View file

@ -12,10 +12,6 @@ body:
changedMetadata: ~ changedMetadata: ~
changedName: ~ changedName: ~
changedProperties: changedProperties:
Attributes:
"!Attributes":
Rojo_Ref_PrimaryPart:
"!String": "./NonExistent/Part"
PrimaryPart: ~ PrimaryPart: ~
id: id-3 id: id-3
packetType: messages packetType: messages

View file

@ -12,10 +12,6 @@ body:
changedMetadata: ~ changedMetadata: ~
changedName: ~ changedName: ~
changedProperties: changedProperties:
Attributes:
"!Attributes":
Rojo_Target_PrimaryPart:
"!String": model target
Scale: Scale:
"!Float32": 1 "!Float32": 1
id: id-8 id: id-8

View file

@ -9,7 +9,8 @@ use rbx_dom_weak::{
}; };
use crate::{ use crate::{
variant_eq::variant_eq, RojoRef, REF_PATH_ATTRIBUTE_PREFIX, REF_POINTER_ATTRIBUTE_PREFIX, variant_eq::variant_eq, RojoRef, REF_ID_ATTRIBUTE_NAME, REF_PATH_ATTRIBUTE_PREFIX,
REF_POINTER_ATTRIBUTE_PREFIX,
}; };
use super::{ use super::{
@ -110,6 +111,29 @@ fn compute_property_patches(
let attribute_ref_properties = compute_ref_properties(snapshot, tree, instance.id()); let attribute_ref_properties = compute_ref_properties(snapshot, tree, instance.id());
// Strip Rojo ref transport attributes from the snapshot before comparison.
// finalize_patch_application strips these from the tree after resolving refs,
// so the snapshot must be stripped too to prevent spurious diffs on duplicate
// VFS events (common on macOS). The refs were already extracted above.
if let Some(Variant::Attributes(attrs)) = snapshot.properties.get_mut(&ustr("Attributes")) {
let keys_to_remove: Vec<String> = attrs
.iter()
.filter_map(|(name, _)| {
if name == REF_ID_ATTRIBUTE_NAME
|| name.starts_with(REF_POINTER_ATTRIBUTE_PREFIX)
|| name.starts_with(REF_PATH_ATTRIBUTE_PREFIX)
{
Some(name.to_string())
} else {
None
}
})
.collect();
for key in keys_to_remove {
attrs.remove(key);
}
}
let changed_name = if snapshot.name == instance.name() { let changed_name = if snapshot.name == instance.name() {
None None
} else { } else {

View file

@ -104,19 +104,22 @@ fn intern_instance_additions(
for (id, added) in additions { for (id, added) in additions {
let parent_id = added.parent; let parent_id = added.parent;
let parent_redacted = redactions.get_redacted_value(parent_id); let parent_redacted = match redactions.get_redacted_value(parent_id) {
Some(v) => v,
// Here, we assume that instances are only added to other instances that None => {
// we've already interned. If that's not true, then we'll have some redactions.intern(parent_id);
// dangling unredacted IDs. redactions.get_redacted_value(parent_id).unwrap()
if let Some(parent_redacted) = parent_redacted { }
added_roots.push((id, parent_redacted)); };
} added_roots.push((id, parent_redacted));
} }
// Sort the input by the redacted key, which should match the traversal // Sort by redacted parent, then by name for deterministic ordering when
// order we need for the tree. // multiple children share the same parent.
added_roots.sort_unstable_by(|a, b| a.1.cmp(&b.1)); added_roots.sort_unstable_by(|a, b| {
a.1.cmp(&b.1)
.then_with(|| additions[a.0].name.cmp(&additions[b.0].name))
});
for (root_id, _redacted_id) in added_roots { for (root_id, _redacted_id) in added_roots {
additions[root_id].intern(redactions, additions); additions[root_id].intern(redactions, additions);