mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 12:45:05 +00:00
151 lines
4.3 KiB
Lua
151 lines
4.3 KiB
Lua
--[[
|
|
Defines the process for diffing a virtual DOM and the real DOM to compute a
|
|
patch that can be later applied.
|
|
]]
|
|
|
|
local Log = require(script.Parent.Parent.Parent.Log)
|
|
local invariant = require(script.Parent.Parent.invariant)
|
|
local getProperty = require(script.Parent.getProperty)
|
|
local Error = require(script.Parent.Error)
|
|
local decodeValue = require(script.Parent.decodeValue)
|
|
|
|
local function isEmpty(table)
|
|
return next(table) == nil
|
|
end
|
|
|
|
local function shouldDeleteUnknownInstances(virtualInstance)
|
|
if virtualInstance.Metadata ~= nil then
|
|
return not virtualInstance.Metadata.ignoreUnknownInstances
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
|
|
local function diff(instanceMap, virtualInstances, rootId)
|
|
local patch = {
|
|
removed = {},
|
|
added = {},
|
|
updated = {},
|
|
}
|
|
|
|
-- Add a virtual instance and all of its descendants to the patch, marked as
|
|
-- being added.
|
|
local function markIdAdded(id)
|
|
local virtualInstance = virtualInstances[id]
|
|
patch.added[id] = virtualInstance
|
|
|
|
for _, childId in ipairs(virtualInstance.Children) do
|
|
markIdAdded(childId)
|
|
end
|
|
end
|
|
|
|
-- Internal recursive kernel for diffing an instance with the given ID.
|
|
local function diffInternal(id)
|
|
local virtualInstance = virtualInstances[id]
|
|
local instance = instanceMap.fromIds[id]
|
|
|
|
if virtualInstance == nil then
|
|
invariant("Cannot diff an instance not present in virtualInstances\nID: {}", id)
|
|
end
|
|
|
|
if instance == nil then
|
|
invariant("Cannot diff an instance not present in InstanceMap\nID: {}", id)
|
|
end
|
|
|
|
local changedClassName = nil
|
|
if virtualInstance.ClassName ~= instance.ClassName then
|
|
changedClassName = virtualInstance.ClassName
|
|
end
|
|
|
|
local changedName = nil
|
|
if virtualInstance.Name ~= instance.Name then
|
|
changedName = virtualInstance.Name
|
|
end
|
|
|
|
local changedProperties = {}
|
|
for propertyName, virtualValue in pairs(virtualInstance.Properties) do
|
|
local ok, existingValueOrErr = getProperty(instance, propertyName)
|
|
|
|
if ok then
|
|
local existingValue = existingValueOrErr
|
|
local ok, decodedValue = decodeValue(virtualValue, instanceMap)
|
|
|
|
if ok then
|
|
if existingValue ~= decodedValue then
|
|
changedProperties[propertyName] = virtualValue
|
|
end
|
|
else
|
|
-- virtualValue can be empty in certain cases, and this may print out nil to the user.
|
|
local propertyType = next(virtualValue)
|
|
Log.warn("Failed to decode property of type {}", propertyType)
|
|
end
|
|
else
|
|
local err = existingValueOrErr
|
|
|
|
if err.kind == Error.UnknownProperty then
|
|
Log.trace("Skipping unknown property {}.{}", err.details.className, err.details.propertyName)
|
|
elseif err.kind == Error.UnreadableProperty then
|
|
Log.trace("Skipping unreadable property {}.{}", err.details.className, err.details.propertyName)
|
|
else
|
|
return false, err
|
|
end
|
|
end
|
|
end
|
|
|
|
if changedName ~= nil or changedClassName ~= nil or not isEmpty(changedProperties) then
|
|
table.insert(patch.updated, {
|
|
id = id,
|
|
changedName = changedName,
|
|
changedClassName = changedClassName,
|
|
changedProperties = changedProperties,
|
|
changedMetadata = nil,
|
|
})
|
|
end
|
|
|
|
-- Traverse the list of children in the DOM. Any instance that has no
|
|
-- corresponding virtual instance should be removed. Any instance that
|
|
-- does have a corresponding virtual instance is recursively diffed.
|
|
for _, childInstance in ipairs(instance:GetChildren()) do
|
|
local childId = instanceMap.fromInstances[childInstance]
|
|
|
|
if childId == nil then
|
|
-- This is an existing instance not present in the virtual DOM.
|
|
-- We can mark it for deletion unless the user has asked us not
|
|
-- to delete unknown stuff.
|
|
if shouldDeleteUnknownInstances(virtualInstance) then
|
|
table.insert(patch.removed, childInstance)
|
|
end
|
|
else
|
|
local ok, err = diffInternal(childId)
|
|
|
|
if not ok then
|
|
return false, err
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Traverse the list of children in the virtual DOM. Any virtual
|
|
-- instance that has no corresponding real instance should be created.
|
|
for _, childId in ipairs(virtualInstance.Children) do
|
|
local childInstance = instanceMap.fromIds[childId]
|
|
|
|
if childInstance == nil then
|
|
-- This instance is present in the virtual DOM, but doesn't
|
|
-- exist in the real DOM.
|
|
markIdAdded(childId)
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local ok, err = diffInternal(rootId)
|
|
|
|
if not ok then
|
|
return false, err
|
|
end
|
|
|
|
return true, patch
|
|
end
|
|
|
|
return diff |