mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 14:01:37 +00:00
Add audit plan for Ambiguous Path Handling feature
- Introduced a comprehensive audit plan detailing critical issues, code quality items, and test coverage gaps identified in the Ambiguous Path Handling feature. - Documented specific todos addressing various correctness and performance issues, including case sensitivity, path formatting, and dead code removal. - Established standards for round-trip identity and CLI syncback parity to ensure consistent behavior across the system. These changes enhance the clarity and accountability of the auditing process, paving the way for improved feature reliability and maintainability.
This commit is contained in:
parent
e313f61515
commit
4cb45cd607
6 changed files with 648 additions and 154 deletions
|
|
@ -189,10 +189,19 @@ function Matching._pass1NameAndClass(
|
|||
end
|
||||
|
||||
--[[
|
||||
Pass 3: Pairwise similarity scoring within same-name groups.
|
||||
Uses ClassName and child count as similarity signals.
|
||||
Structural fingerprinting with XXH32 is computed lazily only for
|
||||
instances that enter this pass.
|
||||
Pass 3: Match remaining instances within same-name groups.
|
||||
|
||||
Strategy:
|
||||
1. Narrow by ClassName within each name group.
|
||||
2. Within same-name+class sub-groups, use POSITIONAL MATCHING as the
|
||||
primary signal. The server sends virtual children in DOM order and
|
||||
Studio's GetChildren() preserves insertion order -- these typically
|
||||
align. The Nth virtual "Line" matches the Nth Studio "Line".
|
||||
3. When group sizes differ (adds/deletes), fall back to property-based
|
||||
similarity scoring to pick the best remaining match.
|
||||
|
||||
This preserves the ordering behavior of the original greedy hydration
|
||||
algorithm while handling ambiguous paths correctly.
|
||||
]]
|
||||
function Matching._pass3Similarity(
|
||||
remainingVirtual: { string },
|
||||
|
|
@ -235,47 +244,58 @@ function Matching._pass3Similarity(
|
|||
continue
|
||||
end
|
||||
|
||||
-- Build all candidate pairs with similarity scores
|
||||
local pairs: { { vi: number, si: number, score: number } } = {}
|
||||
-- Narrow by ClassName within this name group.
|
||||
local virtualByClass: { [string]: { number } } = {}
|
||||
for _, vi in vIndices do
|
||||
if matchedVirtualIndices[vi] then
|
||||
continue
|
||||
end
|
||||
for _, si in sIndices do
|
||||
if matchedStudioIndices[si] then
|
||||
continue
|
||||
local vInst = virtualInstances[remainingVirtual[vi]]
|
||||
if vInst and not matchedVirtualIndices[vi] then
|
||||
local class = vInst.ClassName
|
||||
if not virtualByClass[class] then
|
||||
virtualByClass[class] = {}
|
||||
end
|
||||
local score = Matching._computeSimilarity(
|
||||
remainingVirtual[vi],
|
||||
remainingStudio[si],
|
||||
virtualInstances
|
||||
)
|
||||
table.insert(pairs, { vi = vi, si = si, score = score })
|
||||
table.insert(virtualByClass[class], vi)
|
||||
end
|
||||
end
|
||||
|
||||
-- Sort by score descending (tiebreaker: original order)
|
||||
table.sort(pairs, function(a, b)
|
||||
if a.score ~= b.score then
|
||||
return a.score > b.score
|
||||
end
|
||||
if a.vi ~= b.vi then
|
||||
return a.vi < b.vi
|
||||
end
|
||||
return a.si < b.si
|
||||
end)
|
||||
|
||||
-- Greedy assignment
|
||||
for _, pair in pairs do
|
||||
if matchedVirtualIndices[pair.vi] or matchedStudioIndices[pair.si] then
|
||||
local studioByClass: { [string]: { number } } = {}
|
||||
for _, si in sIndices do
|
||||
if matchedStudioIndices[si] then
|
||||
continue
|
||||
end
|
||||
table.insert(matched, {
|
||||
virtualId = remainingVirtual[pair.vi],
|
||||
studioInstance = remainingStudio[pair.si],
|
||||
})
|
||||
matchedVirtualIndices[pair.vi] = true
|
||||
matchedStudioIndices[pair.si] = true
|
||||
local success, class = pcall(function()
|
||||
return remainingStudio[si].ClassName
|
||||
end)
|
||||
if success and class then
|
||||
if not studioByClass[class] then
|
||||
studioByClass[class] = {}
|
||||
end
|
||||
table.insert(studioByClass[class], si)
|
||||
end
|
||||
end
|
||||
|
||||
-- Within each same-name+class sub-group, do positional matching.
|
||||
-- The Nth virtual instance matches the Nth Studio instance.
|
||||
-- This preserves the original child ordering which is the strongest
|
||||
-- signal for duplicate-named instances (e.g., Parts in a Model).
|
||||
for class, vClassIndices in virtualByClass do
|
||||
local sClassIndices = studioByClass[class]
|
||||
if not sClassIndices then
|
||||
continue
|
||||
end
|
||||
|
||||
local matchCount = math.min(#vClassIndices, #sClassIndices)
|
||||
for idx = 1, matchCount do
|
||||
local vi = vClassIndices[idx]
|
||||
local si = sClassIndices[idx]
|
||||
if not matchedVirtualIndices[vi] and not matchedStudioIndices[si] then
|
||||
table.insert(matched, {
|
||||
virtualId = remainingVirtual[vi],
|
||||
studioInstance = remainingStudio[si],
|
||||
})
|
||||
matchedVirtualIndices[vi] = true
|
||||
matchedStudioIndices[si] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -302,46 +322,4 @@ function Matching._pass3Similarity(
|
|||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Compute similarity between a virtual instance and a Studio instance.
|
||||
Higher score = more similar.
|
||||
|
||||
Comparison order (early-exit, cheapest first):
|
||||
ClassName (100), child count (20), Tags (50), Attributes (30)
|
||||
Source is NOT compared here (too expensive for the matching context).
|
||||
]]
|
||||
function Matching._computeSimilarity(
|
||||
virtualId: string,
|
||||
studioInstance: Instance,
|
||||
virtualInstances: { [string]: any }
|
||||
): number
|
||||
local vInst = virtualInstances[virtualId]
|
||||
if not vInst then
|
||||
return 0
|
||||
end
|
||||
|
||||
local score = 0
|
||||
|
||||
-- ClassName match
|
||||
local classSuccess, className = pcall(function()
|
||||
return studioInstance.ClassName
|
||||
end)
|
||||
if classSuccess and className == vInst.ClassName then
|
||||
score += 100
|
||||
end
|
||||
|
||||
-- Child count match
|
||||
local childSuccess, childCount = pcall(function()
|
||||
return #studioInstance:GetChildren()
|
||||
end)
|
||||
if childSuccess then
|
||||
local vChildCount = if vInst.Children then #vInst.Children else 0
|
||||
if childCount == vChildCount then
|
||||
score += 20
|
||||
end
|
||||
end
|
||||
|
||||
return score
|
||||
end
|
||||
|
||||
return Matching
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue