mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 14:01:37 +00:00
Refactor Reconciler equality checks and enhance path handling
- Introduced a new `trueEquals` function in `trueEquals.lua` for shared value equality checks, improving consistency between `diff.lua` and `matching.lua`. - Removed redundant equality logic from `diff.lua` and `matching.lua`, streamlining the codebase. - Updated path handling in various test snapshots to ensure correct reference values, enhancing the accuracy of reconciliation tests. - Implemented a post-processing step in `syncback` to fix `Rojo_Ref_*` attribute paths with correct dedup suffixes, improving path resolution. These changes enhance the clarity and efficiency of the Reconciler's equality checks and path handling, paving the way for improved project management and user experience.
This commit is contained in:
parent
aabbfb23b2
commit
e207a398b1
14 changed files with 832 additions and 218 deletions
|
|
@ -12,108 +12,12 @@ local getProperty = require(script.Parent.getProperty)
|
|||
local Error = require(script.Parent.Error)
|
||||
local decodeValue = require(script.Parent.decodeValue)
|
||||
|
||||
local trueEquals = require(script.Parent.trueEquals)
|
||||
|
||||
local function isEmpty(table)
|
||||
return next(table) == nil
|
||||
end
|
||||
|
||||
local function fuzzyEq(a: number, b: number, epsilon: number): boolean
|
||||
local diff = math.abs(a - b)
|
||||
-- Use both absolute and relative epsilon for better precision handling
|
||||
-- Absolute: for small numbers near zero
|
||||
-- Relative: for larger numbers where absolute epsilon is too tight
|
||||
local maxVal = math.max(math.abs(a), math.abs(b), 1)
|
||||
return diff < epsilon or diff < maxVal * epsilon
|
||||
end
|
||||
|
||||
local function trueEquals(a, b): boolean
|
||||
-- Exit early for simple equality values
|
||||
if a == b then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Treat nil and { Ref = "000...0" } as equal
|
||||
if
|
||||
(a == nil and type(b) == "table" and b.Ref == "00000000000000000000000000000000")
|
||||
or (b == nil and type(a) == "table" and a.Ref == "00000000000000000000000000000000")
|
||||
then
|
||||
return true
|
||||
end
|
||||
|
||||
local typeA, typeB = typeof(a), typeof(b)
|
||||
|
||||
-- For tables, try recursive deep equality
|
||||
if typeA == "table" and typeB == "table" then
|
||||
local checkedKeys = {}
|
||||
for key, value in a do
|
||||
checkedKeys[key] = true
|
||||
if not trueEquals(value, b[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
for key, value in b do
|
||||
if checkedKeys[key] then
|
||||
continue
|
||||
end
|
||||
if not trueEquals(value, a[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
|
||||
-- For NaN, check if both values are not equal to themselves
|
||||
elseif a ~= a and b ~= b then
|
||||
return true
|
||||
|
||||
-- For numbers, compare with epsilon of 0.0001 to avoid floating point inequality
|
||||
elseif typeA == "number" and typeB == "number" then
|
||||
return fuzzyEq(a, b, 0.0001)
|
||||
|
||||
-- For EnumItem->number, compare the EnumItem's value
|
||||
elseif typeA == "number" and typeB == "EnumItem" then
|
||||
return a == b.Value
|
||||
elseif typeA == "EnumItem" and typeB == "number" then
|
||||
return a.Value == b
|
||||
|
||||
-- For Color3s, compare to RGB ints to avoid floating point inequality
|
||||
elseif typeA == "Color3" and typeB == "Color3" then
|
||||
local aR, aG, aB = math.floor(a.R * 255), math.floor(a.G * 255), math.floor(a.B * 255)
|
||||
local bR, bG, bB = math.floor(b.R * 255), math.floor(b.G * 255), math.floor(b.B * 255)
|
||||
return aR == bR and aG == bG and aB == bB
|
||||
|
||||
-- For CFrames, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
||||
elseif typeA == "CFrame" and typeB == "CFrame" then
|
||||
local aComponents, bComponents = { a:GetComponents() }, { b:GetComponents() }
|
||||
for i, aComponent in aComponents do
|
||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
|
||||
-- For Vector3s, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
||||
elseif typeA == "Vector3" and typeB == "Vector3" then
|
||||
local aComponents, bComponents = { a.X, a.Y, a.Z }, { b.X, b.Y, b.Z }
|
||||
for i, aComponent in aComponents do
|
||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
|
||||
-- For Vector2s, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
||||
elseif typeA == "Vector2" and typeB == "Vector2" then
|
||||
local aComponents, bComponents = { a.X, a.Y }, { b.X, b.Y }
|
||||
for i, aComponent in aComponents do
|
||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function shouldDeleteUnknownInstances(virtualInstance)
|
||||
if virtualInstance.Metadata ~= nil then
|
||||
return not virtualInstance.Metadata.ignoreUnknownInstances
|
||||
|
|
|
|||
|
|
@ -16,89 +16,10 @@
|
|||
local Packages = script.Parent.Parent.Parent.Packages
|
||||
local RbxDom = require(Packages.RbxDom)
|
||||
|
||||
local trueEquals = require(script.Parent.trueEquals)
|
||||
|
||||
local UNMATCHED_PENALTY = 10000
|
||||
|
||||
-- Value equality check. This MUST match the trueEquals function in diff.lua
|
||||
-- exactly. If matching says "equal" but diff says "different", we get phantom
|
||||
-- changes from cross-paired instances.
|
||||
local function fuzzyEq(a: number, b: number, epsilon: number): boolean
|
||||
local diff = math.abs(a - b)
|
||||
local maxVal = math.max(math.abs(a), math.abs(b), 1)
|
||||
return diff < epsilon or diff < maxVal * epsilon
|
||||
end
|
||||
|
||||
local function trueEquals(a, b): boolean
|
||||
if a == b then
|
||||
return true
|
||||
end
|
||||
|
||||
if
|
||||
(a == nil and type(b) == "table" and b.Ref == "00000000000000000000000000000000")
|
||||
or (b == nil and type(a) == "table" and a.Ref == "00000000000000000000000000000000")
|
||||
then
|
||||
return true
|
||||
end
|
||||
|
||||
local typeA, typeB = typeof(a), typeof(b)
|
||||
|
||||
if typeA == "table" and typeB == "table" then
|
||||
local checkedKeys = {}
|
||||
for key, value in a do
|
||||
checkedKeys[key] = true
|
||||
if not trueEquals(value, b[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
for key, value in b do
|
||||
if checkedKeys[key] then
|
||||
continue
|
||||
end
|
||||
if not trueEquals(value, a[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
elseif a ~= a and b ~= b then
|
||||
return true
|
||||
elseif typeA == "number" and typeB == "number" then
|
||||
return fuzzyEq(a, b, 0.0001)
|
||||
elseif typeA == "number" and typeB == "EnumItem" then
|
||||
return a == b.Value
|
||||
elseif typeA == "EnumItem" and typeB == "number" then
|
||||
return a.Value == b
|
||||
elseif typeA == "Color3" and typeB == "Color3" then
|
||||
local aR, aG, aB = math.floor(a.R * 255), math.floor(a.G * 255), math.floor(a.B * 255)
|
||||
local bR, bG, bB = math.floor(b.R * 255), math.floor(b.G * 255), math.floor(b.B * 255)
|
||||
return aR == bR and aG == bG and aB == bB
|
||||
elseif typeA == "CFrame" and typeB == "CFrame" then
|
||||
local aComponents, bComponents = { a:GetComponents() }, { b:GetComponents() }
|
||||
for i, aComponent in aComponents do
|
||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
elseif typeA == "Vector3" and typeB == "Vector3" then
|
||||
local aComponents, bComponents = { a.X, a.Y, a.Z }, { b.X, b.Y, b.Z }
|
||||
for i, aComponent in aComponents do
|
||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
elseif typeA == "Vector2" and typeB == "Vector2" then
|
||||
local aComponents, bComponents = { a.X, a.Y }, { b.X, b.Y }
|
||||
for i, aComponent in aComponents do
|
||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local Matching = {}
|
||||
|
||||
--[[
|
||||
|
|
|
|||
107
plugin/src/Reconciler/trueEquals.lua
Normal file
107
plugin/src/Reconciler/trueEquals.lua
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
--[[
|
||||
Shared value equality check used by both the diff and matching modules.
|
||||
|
||||
If matching says "equal" but diff says "different" (or vice versa),
|
||||
we get phantom changes from cross-paired instances. Both modules MUST
|
||||
use this same function.
|
||||
]]
|
||||
|
||||
local function fuzzyEq(a: number, b: number, epsilon: number): boolean
|
||||
local diff = math.abs(a - b)
|
||||
-- Use both absolute and relative epsilon for better precision handling
|
||||
-- Absolute: for small numbers near zero
|
||||
-- Relative: for larger numbers where absolute epsilon is too tight
|
||||
local maxVal = math.max(math.abs(a), math.abs(b), 1)
|
||||
return diff < epsilon or diff < maxVal * epsilon
|
||||
end
|
||||
|
||||
local function trueEquals(a, b): boolean
|
||||
-- Exit early for simple equality values
|
||||
if a == b then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Treat nil and { Ref = "000...0" } as equal
|
||||
if
|
||||
(a == nil and type(b) == "table" and b.Ref == "00000000000000000000000000000000")
|
||||
or (b == nil and type(a) == "table" and a.Ref == "00000000000000000000000000000000")
|
||||
then
|
||||
return true
|
||||
end
|
||||
|
||||
local typeA, typeB = typeof(a), typeof(b)
|
||||
|
||||
-- For tables, try recursive deep equality
|
||||
if typeA == "table" and typeB == "table" then
|
||||
local checkedKeys = {}
|
||||
for key, value in a do
|
||||
checkedKeys[key] = true
|
||||
if not trueEquals(value, b[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
for key, value in b do
|
||||
if checkedKeys[key] then
|
||||
continue
|
||||
end
|
||||
if not trueEquals(value, a[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
|
||||
-- For NaN, check if both values are not equal to themselves
|
||||
elseif a ~= a and b ~= b then
|
||||
return true
|
||||
|
||||
-- For numbers, compare with epsilon of 0.0001 to avoid floating point inequality
|
||||
elseif typeA == "number" and typeB == "number" then
|
||||
return fuzzyEq(a, b, 0.0001)
|
||||
|
||||
-- For EnumItem->number, compare the EnumItem's value
|
||||
elseif typeA == "number" and typeB == "EnumItem" then
|
||||
return a == b.Value
|
||||
elseif typeA == "EnumItem" and typeB == "number" then
|
||||
return a.Value == b
|
||||
|
||||
-- For Color3s, compare to RGB ints to avoid floating point inequality
|
||||
elseif typeA == "Color3" and typeB == "Color3" then
|
||||
local aR, aG, aB = math.floor(a.R * 255), math.floor(a.G * 255), math.floor(a.B * 255)
|
||||
local bR, bG, bB = math.floor(b.R * 255), math.floor(b.G * 255), math.floor(b.B * 255)
|
||||
return aR == bR and aG == bG and aB == bB
|
||||
|
||||
-- For CFrames, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
||||
elseif typeA == "CFrame" and typeB == "CFrame" then
|
||||
local aComponents, bComponents = { a:GetComponents() }, { b:GetComponents() }
|
||||
for i, aComponent in aComponents do
|
||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
|
||||
-- For Vector3s, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
||||
elseif typeA == "Vector3" and typeB == "Vector3" then
|
||||
local aComponents, bComponents = { a.X, a.Y, a.Z }, { b.X, b.Y, b.Z }
|
||||
for i, aComponent in aComponents do
|
||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
|
||||
-- For Vector2s, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
||||
elseif typeA == "Vector2" and typeB == "Vector2" then
|
||||
local aComponents, bComponents = { a.X, a.Y }, { b.X, b.Y }
|
||||
for i, aComponent in aComponents do
|
||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return trueEquals
|
||||
Loading…
Add table
Add a link
Reference in a new issue