Add visual diffs to syncing (#603)

* Add user confirmation to initial sync

* Use "Accept" instead of "Confirm"

* Draw tree alphabetically for determinism

* Add diff table dropdown

* Add diff table to newly added objects

* Unblock keybind workflow

* Only show reject button when two way is enabled

* Try to patch back to the files when changes are rejected

* Improve text spacing of the prop diff table

* Skip user confirmation of perfect syncs

* Give instances names for debugging UI

* Optimize tree building

* Efficiency: dynamic virtual scrolling & lazy rendering

* Simplify virtual scroller logic and avoid wasteful rerenders

* Remove debug print

* Consistent naming

* Move new patch applied callback into accept

* Pcall archivable

* Keybinds open popup diff window

* Theme rows in diff

* Remove relic of prototype

* Color value visuals and better component name

* changeBatcher is not needed when no sync is active

* Simplify popup roact entrypoint

* Alphabetical prop lists and refactor

* Add a stroke to color blot for contrast

* Make color blots animate transparency with the rest of the page

* StyLua formatting on newly added files

* Remove wasteful table

* Fix diffing custom properties

* Display tables more meaningfully

* Allow children in the button components

* Create a rough tooltip component

* Add tooltips to buttons

* Use provider+trigger schema to avoid tooltip ZIndex issues

* Add triangle point to tooltip

* Tooltip underneath instead of covering

* Cancel hovers when unmounting

* Allow multiple canvases from one provider

* Display above or below depending on available space

* Move patch equality to PatchSet.isEqual

* Use Container

* Remove old submodules

* Reduce false positives in diff

* Add debug log

* Fuzzy equals CFrame in diffs to avoid floating point in

* Fix decodeValue usage

* Support the .changedName patches

* Fix content overlapping border

* Fix tooltip tail alignment

* Fix tooltip text fit

* Whoops, fix it properly

* Move PatchVisualizer to Components

* Provide Connected info with full patch data

* Avoid implicit nil return

* Add patch visualizer to connected page

* Make Current column invisible when visualizing applied patches

* Avoid floating point diffs in a numbers and vectors
This commit is contained in:
boatbomber
2023-04-01 23:17:23 -04:00
committed by GitHub
parent 7994bc4909
commit b5ed952d5c
19 changed files with 1618 additions and 81 deletions

View File

@@ -15,6 +15,86 @@ local function isEmpty(table)
return next(table) == nil
end
local function fuzzyEq(a: number, b: number, epsilon: number): boolean
return math.abs(a - b) < epsilon
end
local function trueEquals(a, b): boolean
-- Exit early for simple equality values
if a == b 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 pairs(a) do
checkedKeys[key] = true
if not trueEquals(value, b[key]) then
return false
end
end
for key, value in pairs(b) do
if checkedKeys[key] then continue end
if not trueEquals(value, a[key]) then
return false
end
end
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
@@ -73,7 +153,8 @@ local function diff(instanceMap, virtualInstances, rootId)
local ok, decodedValue = decodeValue(virtualValue, instanceMap)
if ok then
if existingValue ~= decodedValue then
if not trueEquals(existingValue, decodedValue) then
Log.debug("{}.{} changed from '{}' to '{}'", instance:GetFullName(), propertyName, existingValue, decodedValue)
changedProperties[propertyName] = virtualValue
end
else