forked from rojo-rbx/rojo
Break apart plugin reconciler (#332)
* Start splitting apart reconciler, with tests * Reify children in reify * Baseline hydrate implementation * Remove debug print * Scaffold out diff implementation, just supporting name changes * invariant -> error in decodeValue * Flesh out diff and add getProperty * Clear out top-level reconciler interface, start updating code that touches it * Address review feedback * Add (experimental) Selene configuration * Add emptiness checks to PatchSet, remove unimplement invert method * Improve descendant destruction behavior in InstanceMap * Track instanceId on all reify errors * Base implementation of applyPatch, returning partial patches on failure * Change reify to accept InstanceMap and insert instances into it * Start testing applyPatch for removals * Add test for applyPatch adding instances successfully and not * Add , which is just error with formatting * Correctly use new diff and applyPatch APIs * Improve applyPatch logging and fix field name typo * Better debug output when reify fails * Print out unapplied patch in debug mode * Don't write properties if their values are not different. This was exposed trying to sync the Rojo plugin, which has a gigantic ModuleScript in it with the reflection database. This workaround was present in some form in many versions of Rojo, and I guess we still need it. This time, I actually documented why it's here so that I don't forget for the umpteenth time... * Add placeholder test that needs to happen still * Introduce easier plugin testing, write applyPatch properties test * Delete legacy get/setCanonicalProperty files * Fix trying to remove numbers instead of instances * Change applyPatch to return partial patches instead of binary success * Work towards being able to decode and apply refs * Add helpers for PatchSet assertions * Apply refs in reify, test all cases * Improve diagnostics when patches fail to apply * Stop logging when destroying untracked instances, it's ok * Remove read before setting property in applyPatch * Fix diff thinking all properties are changed
This commit is contained in:
committed by
GitHub
parent
50f0a2bd2e
commit
f66860bdfe
131
plugin/src/Reconciler/applyPatch.lua
Normal file
131
plugin/src/Reconciler/applyPatch.lua
Normal file
@@ -0,0 +1,131 @@
|
||||
--[[
|
||||
Apply a patch to the DOM. Returns any portions of the patch that weren't
|
||||
possible to apply.
|
||||
|
||||
Patches can come from the server or be generated by the client.
|
||||
]]
|
||||
|
||||
local Log = require(script.Parent.Parent.Parent.Log)
|
||||
|
||||
local PatchSet = require(script.Parent.Parent.PatchSet)
|
||||
local Types = require(script.Parent.Parent.Types)
|
||||
local invariant = require(script.Parent.Parent.invariant)
|
||||
|
||||
local decodeValue = require(script.Parent.decodeValue)
|
||||
local getProperty = require(script.Parent.getProperty)
|
||||
local reify = require(script.Parent.reify)
|
||||
local setProperty = require(script.Parent.setProperty)
|
||||
|
||||
local function applyPatch(instanceMap, patch)
|
||||
-- Tracks any portions of the patch that could not be applied to the DOM.
|
||||
local unappliedPatch = PatchSet.newEmpty()
|
||||
|
||||
for _, removedIdOrInstance in ipairs(patch.removed) do
|
||||
if Types.RbxId(removedIdOrInstance) then
|
||||
instanceMap:destroyId(removedIdOrInstance)
|
||||
else
|
||||
instanceMap:destroyInstance(removedIdOrInstance)
|
||||
end
|
||||
end
|
||||
|
||||
for id, virtualInstance in pairs(patch.added) do
|
||||
if instanceMap.fromIds[id] ~= nil then
|
||||
-- This instance already exists. We might've already added it in a
|
||||
-- previous iteration of this loop, or maybe this patch was not
|
||||
-- supposed to list this instance.
|
||||
--
|
||||
-- It's probably fine, right?
|
||||
continue
|
||||
end
|
||||
|
||||
-- Find the first ancestor of this instance that is marked for an
|
||||
-- addition.
|
||||
--
|
||||
-- This helps us make sure we only reify each instance once, and we
|
||||
-- start from the top.
|
||||
while patch.added[virtualInstance.Parent] ~= nil do
|
||||
id = virtualInstance.Parent
|
||||
virtualInstance = patch.added[id]
|
||||
end
|
||||
|
||||
local parentInstance = instanceMap.fromIds[virtualInstance.Parent]
|
||||
|
||||
if parentInstance == nil then
|
||||
-- This would be peculiar. If you create an instance with no
|
||||
-- parent, were you supposed to create it at all?
|
||||
invariant(
|
||||
"Cannot add an instance from a patch that has no parent.\nInstance {} with parent {}.\nState: {:#?}",
|
||||
id,
|
||||
virtualInstance.Parent,
|
||||
instanceMap
|
||||
)
|
||||
end
|
||||
|
||||
local failedToReify = reify(instanceMap, patch.added, id, parentInstance)
|
||||
|
||||
if not PatchSet.isEmpty(failedToReify) then
|
||||
Log.debug("Failed to reify as part of applying a patch: {}", failedToReify)
|
||||
PatchSet.assign(unappliedPatch, failedToReify)
|
||||
end
|
||||
end
|
||||
|
||||
for _, update in ipairs(patch.updated) do
|
||||
local instance = instanceMap.fromIds[update.id]
|
||||
|
||||
if instance == nil then
|
||||
-- We can't update an instance that doesn't exist.
|
||||
-- TODO: Should this be an invariant?
|
||||
continue
|
||||
end
|
||||
|
||||
-- Track any part of this update that could not be applied.
|
||||
local unappliedUpdate = {
|
||||
id = update.id,
|
||||
changedProperties = {},
|
||||
}
|
||||
local partiallyApplied = false
|
||||
|
||||
if update.changedClassName ~= nil then
|
||||
-- TODO: Support changing class name by destroying + recreating.
|
||||
unappliedUpdate.changedClassName = update.changedClassName
|
||||
partiallyApplied = true
|
||||
end
|
||||
|
||||
if update.changedName ~= nil then
|
||||
instance.Name = update.changedName
|
||||
end
|
||||
|
||||
if update.changedMetadata ~= nil then
|
||||
-- TODO: Support changing metadata. This will become necessary when
|
||||
-- Rojo persistently tracks metadata for each instance in order to
|
||||
-- remove extra instances.
|
||||
unappliedUpdate.changedMetadata = update.changedMetadata
|
||||
partiallyApplied = true
|
||||
end
|
||||
|
||||
if update.changedProperties ~= nil then
|
||||
for propertyName, propertyValue in pairs(update.changedProperties) do
|
||||
local ok, decodedValue = decodeValue(propertyValue, instanceMap)
|
||||
if not ok then
|
||||
unappliedUpdate.changedProperties[propertyName] = propertyValue
|
||||
partiallyApplied = true
|
||||
continue
|
||||
end
|
||||
|
||||
local ok = setProperty(instance, propertyName, decodedValue)
|
||||
if not ok then
|
||||
unappliedUpdate.changedProperties[propertyName] = propertyValue
|
||||
partiallyApplied = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if partiallyApplied then
|
||||
table.insert(unappliedPatch.updated, unappliedUpdate)
|
||||
end
|
||||
end
|
||||
|
||||
return unappliedPatch
|
||||
end
|
||||
|
||||
return applyPatch
|
||||
Reference in New Issue
Block a user