mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 22:11:38 +00:00
Complete implementation of the 3-pass matching algorithm for ambiguous path handling
- Finalized the transition from a duplicate-name blacklist system to a comprehensive 3-pass matching algorithm for both syncback and forward operations, enhancing the handling of ambiguous paths. - Integrated a new `matching.lua` module to facilitate instance matching during hydration, utilizing unique name matching, class name narrowing, and structural fingerprinting. - Updated various components, including `ChangeBatcher`, `Reconciler`, and `snapshot` modules, to leverage the new matching logic, ensuring robust file management and improved performance. - Removed legacy duplicate handling code and streamlined the encoding process for instances with duplicate names. These changes significantly enhance the clarity and efficiency of file operations, paving the way for improved project management and user experience.
This commit is contained in:
parent
cc77a2cd8a
commit
d6162ac210
21 changed files with 2507 additions and 846 deletions
|
|
@ -1,12 +1,18 @@
|
|||
--[[
|
||||
Defines the process of "hydration" -- matching up a virtual DOM with
|
||||
concrete instances and assigning them IDs.
|
||||
|
||||
Uses the 3-pass matching algorithm to handle duplicate-named instances:
|
||||
Pass 1: unique name + ClassName narrowing
|
||||
Pass 2: Ref property discriminators (using pre-matched UIDs)
|
||||
Pass 3: structural fingerprinting + pairwise similarity
|
||||
]]
|
||||
|
||||
local Packages = script.Parent.Parent.Parent.Packages
|
||||
local Log = require(Packages.Log)
|
||||
|
||||
local invariant = require(script.Parent.Parent.invariant)
|
||||
local Matching = require(script.Parent.matching)
|
||||
|
||||
local function hydrate(instanceMap, virtualInstances, rootId, rootInstance)
|
||||
local virtualInstance = virtualInstances[rootId]
|
||||
|
|
@ -18,50 +24,40 @@ local function hydrate(instanceMap, virtualInstances, rootId, rootInstance)
|
|||
instanceMap:insert(rootId, rootInstance)
|
||||
|
||||
local existingChildren = rootInstance:GetChildren()
|
||||
local virtualChildIds = virtualInstance.Children
|
||||
|
||||
-- For each existing child, we'll track whether it's been paired with an
|
||||
-- instance that the Rojo server knows about.
|
||||
local isExistingChildVisited = {}
|
||||
for i = 1, #existingChildren do
|
||||
isExistingChildVisited[i] = false
|
||||
end
|
||||
|
||||
for _, childId in ipairs(virtualInstance.Children) do
|
||||
local virtualChild = virtualInstances[childId]
|
||||
|
||||
if virtualChild == nil then
|
||||
-- Filter out virtual children missing from virtualInstances
|
||||
local validVirtualIds: { string } = {}
|
||||
for _, childId in ipairs(virtualChildIds) do
|
||||
if virtualInstances[childId] then
|
||||
table.insert(validVirtualIds, childId)
|
||||
else
|
||||
Log.warn(
|
||||
"Hydration: virtualInstances missing child ID {} (parent: {} '{}')",
|
||||
childId,
|
||||
rootId,
|
||||
virtualInstance.Name
|
||||
)
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
local matched = false
|
||||
for childIndex, childInstance in existingChildren do
|
||||
if not isExistingChildVisited[childIndex] then
|
||||
-- We guard accessing Name and ClassName in order to avoid
|
||||
-- tripping over children of DataModel that Rojo won't have
|
||||
-- permissions to access at all.
|
||||
local accessSuccess, name, className = pcall(function()
|
||||
return childInstance.Name, childInstance.ClassName
|
||||
end)
|
||||
-- Use the 3-pass matching algorithm to pair virtual ↔ studio children
|
||||
local result = Matching.matchChildren(
|
||||
validVirtualIds,
|
||||
existingChildren,
|
||||
virtualInstances,
|
||||
instanceMap
|
||||
)
|
||||
|
||||
-- This rule is very conservative and could be loosened in the
|
||||
-- future, or more heuristics could be introduced.
|
||||
if accessSuccess and name == virtualChild.Name and className == virtualChild.ClassName then
|
||||
isExistingChildVisited[childIndex] = true
|
||||
hydrate(instanceMap, virtualInstances, childId, childInstance)
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Recursively hydrate matched pairs
|
||||
for _, pair in result.matched do
|
||||
hydrate(instanceMap, virtualInstances, pair.virtualId, pair.studioInstance)
|
||||
end
|
||||
|
||||
if not matched then
|
||||
-- Log why the match failed to help diagnose hydration issues
|
||||
-- Log unmatched virtual children (no Studio counterpart found)
|
||||
for _, virtualId in result.unmatchedVirtual do
|
||||
local virtualChild = virtualInstances[virtualId]
|
||||
if virtualChild then
|
||||
Log.debug(
|
||||
"Hydration: No match for virtual child '{}' ({}) under '{}' ({})",
|
||||
virtualChild.Name,
|
||||
|
|
@ -69,17 +65,6 @@ local function hydrate(instanceMap, virtualInstances, rootId, rootInstance)
|
|||
virtualInstance.Name,
|
||||
rootInstance:GetFullName()
|
||||
)
|
||||
-- List real children for comparison
|
||||
local realChildNames = {}
|
||||
for _, child in existingChildren do
|
||||
local success, childName, childClass = pcall(function()
|
||||
return child.Name, child.ClassName
|
||||
end)
|
||||
if success then
|
||||
table.insert(realChildNames, string.format("%s (%s)", childName, childClass))
|
||||
end
|
||||
end
|
||||
Log.trace(" Real children: {}", table.concat(realChildNames, ", "))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue