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:
Lucien Greathouse
2020-11-11 16:30:23 -08:00
committed by GitHub
parent 50f0a2bd2e
commit f66860bdfe
26 changed files with 1818 additions and 466 deletions

View 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