Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes issues with refs in the plugin. #1005

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@

# Snapshot files from the 'insta' Rust crate
**/*.snap.new

# Macos file system junk
._*
.DS_STORE
24 changes: 22 additions & 2 deletions plugin/src/Reconciler/applyPatch.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ local invariant = require(script.Parent.Parent.invariant)

local decodeValue = require(script.Parent.decodeValue)
local reify = require(script.Parent.reify)
local reifyInstance, applyDeferredRefs = reify.reifyInstance, reify.applyDeferredRefs
local setProperty = require(script.Parent.setProperty)

local function applyPatch(instanceMap, patch)
Expand All @@ -28,6 +29,11 @@ local function applyPatch(instanceMap, patch)

-- Tracks any portions of the patch that could not be applied to the DOM.
local unappliedPatch = PatchSet.newEmpty()

-- Contains a list of all of the ref properties that we'll need to assign.
-- It is imperative that refs are assigned after all instances are created
-- to ensure that referents can be mapped to instances correctly.
local deferredRefs = {}

for _, removedIdOrInstance in ipairs(patch.removed) do
local removeInstanceSuccess = pcall(function()
Expand Down Expand Up @@ -78,7 +84,7 @@ local function applyPatch(instanceMap, patch)
)
end

local failedToReify = reify(instanceMap, patch.added, id, parentInstance)
local failedToReify = reifyInstance(deferredRefs, instanceMap, patch.added, id, parentInstance)

if not PatchSet.isEmpty(failedToReify) then
Log.debug("Failed to reify as part of applying a patch: {:#?}", failedToReify)
Expand Down Expand Up @@ -143,7 +149,7 @@ local function applyPatch(instanceMap, patch)
[update.id] = mockVirtualInstance,
}

local failedToReify = reify(instanceMap, mockAdded, update.id, instance.Parent)
local failedToReify = reifyInstance(deferredRefs, instanceMap, mockAdded, update.id, instance.Parent)

local newInstance = instanceMap.fromIds[update.id]

Expand Down Expand Up @@ -206,6 +212,18 @@ local function applyPatch(instanceMap, patch)

if update.changedProperties ~= nil then
for propertyName, propertyValue in pairs(update.changedProperties) do
-- Because refs may refer to instances that we haven't constructed yet,
-- we defer applying any ref properties until all instances are created.
if next(propertyValue) == "Ref" then
table.insert(deferredRefs, {
id = update.id,
instance = instance,
propertyName = propertyName,
virtualValue = propertyValue,
})
continue
end

local decodeSuccess, decodedValue = decodeValue(propertyValue, instanceMap)
if not decodeSuccess then
unappliedUpdate.changedProperties[propertyName] = propertyValue
Expand All @@ -230,6 +248,8 @@ local function applyPatch(instanceMap, patch)
ChangeHistoryService:FinishRecording(historyRecording, Enum.FinishRecordingOperation.Commit)
end

applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)

return unappliedPatch
end

Expand Down
20 changes: 19 additions & 1 deletion plugin/src/Reconciler/diff.lua
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,25 @@ local function diff(instanceMap, virtualInstances, rootId)

if getProperySuccess then
local existingValue = existingValueOrErr
local decodeSuccess, decodedValue = decodeValue(virtualValue, instanceMap)
local decodeSuccess, decodedValue

-- If `virtualValue` is a ref then instead of decoding it to an instance,
-- we change `existingValue` to be a ref. This is because `virtualValue`
-- may point to an Instance which doesn't exist yet and therefore
-- decoding it may throw an error.
if next(virtualValue) == "Ref" then
decodeSuccess, decodedValue = true, virtualValue

if existingValue and typeof(existingValue) == "Instance" then
local existingValueRef = instanceMap.fromInstances[existingValue]
if existingValueRef then
existingValue = { Ref = existingValueRef }
end
end

else
decodeSuccess, decodedValue = decodeValue(virtualValue, instanceMap)
end

if decodeSuccess then
if not trueEquals(existingValue, decodedValue) then
Expand Down
45 changes: 20 additions & 25 deletions plugin/src/Reconciler/reify.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,6 @@ local PatchSet = require(script.Parent.Parent.PatchSet)
local setProperty = require(script.Parent.setProperty)
local decodeValue = require(script.Parent.decodeValue)

local reifyInner, applyDeferredRefs

local function reify(instanceMap, virtualInstances, rootId, parentInstance)
-- Create an empty patch that will be populated with any parts of this reify
-- that could not happen, like instances that couldn't be created and
-- properties that could not be assigned.
local unappliedPatch = PatchSet.newEmpty()

-- Contains a list of all of the ref properties that we'll need to assign
-- after all instances are created. We apply refs in a second pass, after
-- we create as many instances as we can, so that we ensure that referents
-- can be mapped to instances correctly.
local deferredRefs = {}

reifyInner(instanceMap, virtualInstances, rootId, parentInstance, unappliedPatch, deferredRefs)
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)

return unappliedPatch
end

--[[
Add the given ID and all of its descendants in virtualInstances to the given
PatchSet, marked for addition.
Expand All @@ -40,10 +20,21 @@ local function addAllToPatch(patchSet, virtualInstances, id)
end
end

function reifyInstance(deferredRefs, instanceMap, virtualInstances, rootId, parentInstance)
-- Create an empty patch that will be populated with any parts of this reify
-- that could not happen, like instances that couldn't be created and
-- properties that could not be assigned.
local unappliedPatch = PatchSet.newEmpty()

reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, rootId, parentInstance)

return unappliedPatch
end

--[[
Inner function that defines the core routine.
]]
function reifyInner(instanceMap, virtualInstances, id, parentInstance, unappliedPatch, deferredRefs)
function reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, id, parentInstance)
local virtualInstance = virtualInstances[id]

if virtualInstance == nil then
Expand All @@ -56,7 +47,7 @@ function reifyInner(instanceMap, virtualInstances, id, parentInstance, unapplied
local createSuccess, instance = pcall(Instance.new, virtualInstance.ClassName)

if not createSuccess then
addAllToPatch(unappliedPatch, virtualInstances, id)
addAllToPatch(virtualInstances, id)
return
end

Expand Down Expand Up @@ -102,7 +93,7 @@ function reifyInner(instanceMap, virtualInstances, id, parentInstance, unapplied
end

for _, childId in ipairs(virtualInstance.Children) do
reifyInner(instanceMap, virtualInstances, childId, instance, unappliedPatch, deferredRefs)
reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, childId, instance)
end

instance.Parent = parentInstance
Expand Down Expand Up @@ -137,12 +128,13 @@ function applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)

for _, entry in ipairs(deferredRefs) do
local _, refId = next(entry.virtualValue)

if refId == nil then
continue
end

local targetInstance = instanceMap.fromIds[refId]

if targetInstance == nil then
markFailed(entry.id, entry.propertyName, entry.virtualValue)
continue
Expand All @@ -155,4 +147,7 @@ function applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
end
end

return reify
return {
reifyInstance = reifyInstance,
applyDeferredRefs = applyDeferredRefs
}
23 changes: 12 additions & 11 deletions plugin/src/Reconciler/reify.spec.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
return function()
local reify = require(script.Parent.reify)
local reifyInstance, applyDeferredRefs = reify.reifyInstance, reify.applyDeferredRefs

local PatchSet = require(script.Parent.Parent.PatchSet)
local InstanceMap = require(script.Parent.Parent.InstanceMap)
Expand All @@ -20,7 +21,7 @@ return function()

it("should throw when given a bogus ID", function()
expect(function()
reify(InstanceMap.new(), {}, "Hi, mom!", game)
reifyInstance({}, InstanceMap.new(), {}, "Hi, mom!", game)
end).to.throw()
end)

Expand All @@ -35,7 +36,7 @@ return function()
}

local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT", nil)
local unappliedPatch = reifyInstance({}, instanceMap, virtualInstances, "ROOT", nil)

assert(instanceMap:size() == 0, "expected instanceMap to be empty")

Expand All @@ -61,7 +62,7 @@ return function()
}

local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
local unappliedPatch = reifyInstance({}, instanceMap, virtualInstances, "ROOT")

assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")

Expand Down Expand Up @@ -91,7 +92,7 @@ return function()
}

local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
local unappliedPatch = reifyInstance({}, instanceMap, virtualInstances, "ROOT")

assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")

Expand Down Expand Up @@ -123,7 +124,7 @@ return function()
}

local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
local unappliedPatch = reifyInstance({}, instanceMap, virtualInstances, "ROOT")

expect(size(unappliedPatch.added)).to.equal(1)
expect(unappliedPatch.added["CHILD"]).to.equal(virtualInstances["CHILD"])
Expand Down Expand Up @@ -154,7 +155,7 @@ return function()
}

local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
local unappliedPatch = reifyInstance({}, instanceMap, virtualInstances, "ROOT")

local instance = instanceMap.fromIds["ROOT"]
expect(instance.ClassName).to.equal("StringValue")
Expand Down Expand Up @@ -197,7 +198,7 @@ return function()
}

local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
local unappliedPatch = reifyInstance({}, instanceMap, virtualInstances, "ROOT")

assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")

Expand Down Expand Up @@ -229,7 +230,7 @@ return function()
existing.Name = "Existing"
instanceMap:insert("EXISTING", existing)

local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
local unappliedPatch = reifyInstance({}, instanceMap, virtualInstances, "ROOT")

assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")

Expand Down Expand Up @@ -269,7 +270,7 @@ return function()
}

local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
local unappliedPatch = reifyInstance({}, instanceMap, virtualInstances, "ROOT")

assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")

Expand Down Expand Up @@ -308,7 +309,7 @@ return function()
}

local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
local unappliedPatch = reifyInstance({}, instanceMap, virtualInstances, "ROOT")

assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")

Expand All @@ -333,7 +334,7 @@ return function()
}

local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
local unappliedPatch = reifyInstance({}, instanceMap, virtualInstances, "ROOT")

assert(not PatchSet.hasRemoves(unappliedPatch), "expected no removes")
assert(not PatchSet.hasAdditions(unappliedPatch), "expected no additions")
Expand Down