mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-24 06:34:09 +00:00
Add Luau-style ref paths migration plan and update related tests
- Introduced a new migration plan for converting `Rojo_Ref_*` attribute paths to Luau require-by-string style, enhancing portability with relative paths. - Updated existing test snapshots to reflect the new path formats, ensuring consistency across various scenarios. - Enhanced the `compute_relative_ref_path` and `resolve_ref_path` functions to support the new path semantics, improving path resolution logic. - Refactored related code in `change_processor`, `syncback`, and `snapshot` modules to accommodate the new reference path handling. These changes improve the robustness and maintainability of reference path management in the project.
This commit is contained in:
parent
d6169c6d84
commit
b8c3520d25
29 changed files with 2147 additions and 179 deletions
|
|
@ -144,7 +144,7 @@ fn finalize_patch_application(context: PatchApplyContext, tree: &mut RojoTree) -
|
|||
let mut path_rewrites = Vec::new();
|
||||
for (id, map) in context.path_refs_to_rewrite {
|
||||
for (prop_name, path) in map {
|
||||
if let Some(target) = tree.get_instance_by_path(&path) {
|
||||
if let Some(target) = tree.resolve_ref_path(&path, id) {
|
||||
log::debug!(
|
||||
"Resolved path reference {} -> {} (path: {})",
|
||||
prop_name,
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ fn compute_property_patches(
|
|||
let mut visited_properties = UstrSet::default();
|
||||
let mut changed_properties = UstrMap::new();
|
||||
|
||||
let attribute_ref_properties = compute_ref_properties(snapshot, tree);
|
||||
let attribute_ref_properties = compute_ref_properties(snapshot, tree, instance.id());
|
||||
|
||||
let changed_name = if snapshot.name == instance.name() {
|
||||
None
|
||||
|
|
@ -251,6 +251,7 @@ fn compute_children_patches(
|
|||
fn compute_ref_properties(
|
||||
snapshot: &InstanceSnapshot,
|
||||
tree: &RojoTree,
|
||||
source_ref: Ref,
|
||||
) -> UstrMap<Option<Variant>> {
|
||||
let mut map = UstrMap::new();
|
||||
let attributes = match snapshot.properties.get(&ustr("Attributes")) {
|
||||
|
|
@ -290,7 +291,7 @@ fn compute_ref_properties(
|
|||
let Some(path) = crate::variant_as_str(attr_value, attr_name) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(target_id) = tree.get_instance_by_path(path) {
|
||||
if let Some(target_id) = tree.resolve_ref_path(path, source_ref) {
|
||||
map.insert(ustr(prop_name), Some(Variant::Ref(target_id)));
|
||||
} else {
|
||||
// Path not found yet - will be resolved during patch apply
|
||||
|
|
|
|||
|
|
@ -285,6 +285,109 @@ impl RojoTree {
|
|||
Some(current_ref)
|
||||
}
|
||||
|
||||
/// Resolve a Luau require-by-string style ref path to a target instance.
|
||||
///
|
||||
/// `source_ref` is the instance that owns the `Rojo_Ref_*` attribute.
|
||||
/// Prefix semantics (per Roblox require-by-string):
|
||||
/// - `@game/rest` -- resolve from DataModel root
|
||||
/// - `@self` -- return source_ref
|
||||
/// - `@self/rest` -- resolve from source_ref
|
||||
/// - `./rest` -- resolve from source_ref's parent
|
||||
/// - `../rest` -- resolve from source_ref's grandparent
|
||||
/// - bare path (no prefix) -- legacy fallback, resolve like `@game/`
|
||||
///
|
||||
/// During segment walk, `..` navigates to the parent.
|
||||
pub fn resolve_ref_path(&self, path: &str, source_ref: Ref) -> Option<Ref> {
|
||||
if let Some(rest) = path.strip_prefix("@game/") {
|
||||
return self.walk_segments(self.get_root_id(), rest);
|
||||
}
|
||||
if path == "@game" {
|
||||
return Some(self.get_root_id());
|
||||
}
|
||||
if path == "@self" {
|
||||
return Some(source_ref);
|
||||
}
|
||||
if let Some(rest) = path.strip_prefix("@self/") {
|
||||
return self.walk_segments(source_ref, rest);
|
||||
}
|
||||
if let Some(rest) = path.strip_prefix("./") {
|
||||
let parent = self.inner.get_by_ref(source_ref)?.parent();
|
||||
if parent.is_none() {
|
||||
return None;
|
||||
}
|
||||
return self.walk_segments(parent, rest);
|
||||
}
|
||||
if let Some(rest) = path.strip_prefix("../") {
|
||||
let parent = self.inner.get_by_ref(source_ref)?.parent();
|
||||
if parent.is_none() {
|
||||
return None;
|
||||
}
|
||||
let grandparent = self.inner.get_by_ref(parent)?.parent();
|
||||
if grandparent.is_none() {
|
||||
return None;
|
||||
}
|
||||
return self.walk_segments(grandparent, rest);
|
||||
}
|
||||
|
||||
// Bare path (no prefix) -- legacy backward compatibility
|
||||
self.walk_segments(self.get_root_id(), path)
|
||||
}
|
||||
|
||||
/// Walk `/`-separated segments from a starting Ref, handling `..` as
|
||||
/// "go to parent". Each non-`..` segment matches a child by filesystem
|
||||
/// name (case-insensitive), falling back to instance name.
|
||||
fn walk_segments(&self, start: Ref, rest: &str) -> Option<Ref> {
|
||||
if rest.is_empty() {
|
||||
return Some(start);
|
||||
}
|
||||
|
||||
let mut current_ref = start;
|
||||
for segment in rest.split('/') {
|
||||
if segment.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if segment == ".." {
|
||||
let inst = self.inner.get_by_ref(current_ref)?;
|
||||
let parent = inst.parent();
|
||||
if parent.is_none() {
|
||||
return None;
|
||||
}
|
||||
current_ref = parent;
|
||||
continue;
|
||||
}
|
||||
|
||||
let current = self.inner.get_by_ref(current_ref)?;
|
||||
let children = current.children();
|
||||
|
||||
let mut found = false;
|
||||
for &child_ref in children {
|
||||
let fs_name = self.filesystem_name_for(child_ref);
|
||||
if fs_name.eq_ignore_ascii_case(segment) {
|
||||
current_ref = child_ref;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
for &child_ref in children {
|
||||
let child = self.inner.get_by_ref(child_ref)?;
|
||||
if child.name.eq_ignore_ascii_case(segment) {
|
||||
current_ref = child_ref;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(current_ref)
|
||||
}
|
||||
|
||||
/// Returns the filesystem name for an instance (the filename or directory
|
||||
/// name on disk). Derived from `instigating_source` if available, otherwise
|
||||
/// falls back to the instance name.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue