mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 12:45:05 +00:00
Fixes issues with refs in the plugin. (#1005)
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -19,3 +19,7 @@
|
|||||||
|
|
||||||
# Snapshot files from the 'insta' Rust crate
|
# Snapshot files from the 'insta' Rust crate
|
||||||
**/*.snap.new
|
**/*.snap.new
|
||||||
|
|
||||||
|
# Macos file system junk
|
||||||
|
._*
|
||||||
|
.DS_STORE
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ local function debugImpl(buffer, value, extendedForm)
|
|||||||
elseif valueType == "table" then
|
elseif valueType == "table" then
|
||||||
local valueMeta = getmetatable(value)
|
local valueMeta = getmetatable(value)
|
||||||
|
|
||||||
if valueMeta ~= nil and valueMeta.__fmtDebug ~= nil then
|
if valueMeta ~= nil and valueMeta.__fmtDebug ~= nil then
|
||||||
-- This type implement's the metamethod we made up to line up with
|
-- This type implement's the metamethod we made up to line up with
|
||||||
-- Rust's 'Debug' trait.
|
-- Rust's 'Debug' trait.
|
||||||
|
|
||||||
@@ -242,4 +242,4 @@ return {
|
|||||||
debugOutputBuffer = debugOutputBuffer,
|
debugOutputBuffer = debugOutputBuffer,
|
||||||
fmt = fmt,
|
fmt = fmt,
|
||||||
debugify = debugify,
|
debugify = debugify,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,4 +31,4 @@ function Response:json()
|
|||||||
return HttpService:JSONDecode(self.body)
|
return HttpService:JSONDecode(self.body)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Response
|
return Response
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ return function()
|
|||||||
it("should load", function()
|
it("should load", function()
|
||||||
require(script.Parent)
|
require(script.Parent)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -57,4 +57,4 @@ function Log.error(template, ...)
|
|||||||
error(Fmt.fmt(template, ...))
|
error(Fmt.fmt(template, ...))
|
||||||
end
|
end
|
||||||
|
|
||||||
return Log
|
return Log
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ return function()
|
|||||||
it("should load", function()
|
it("should load", function()
|
||||||
require(script.Parent)
|
require(script.Parent)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ local invariant = require(script.Parent.Parent.invariant)
|
|||||||
|
|
||||||
local decodeValue = require(script.Parent.decodeValue)
|
local decodeValue = require(script.Parent.decodeValue)
|
||||||
local reify = require(script.Parent.reify)
|
local reify = require(script.Parent.reify)
|
||||||
|
local reifyInstance, applyDeferredRefs = reify.reifyInstance, reify.applyDeferredRefs
|
||||||
local setProperty = require(script.Parent.setProperty)
|
local setProperty = require(script.Parent.setProperty)
|
||||||
|
|
||||||
local function applyPatch(instanceMap, patch)
|
local function applyPatch(instanceMap, patch)
|
||||||
@@ -29,6 +30,11 @@ local function applyPatch(instanceMap, patch)
|
|||||||
-- Tracks any portions of the patch that could not be applied to the DOM.
|
-- Tracks any portions of the patch that could not be applied to the DOM.
|
||||||
local unappliedPatch = PatchSet.newEmpty()
|
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
|
for _, removedIdOrInstance in ipairs(patch.removed) do
|
||||||
local removeInstanceSuccess = pcall(function()
|
local removeInstanceSuccess = pcall(function()
|
||||||
if Types.RbxId(removedIdOrInstance) then
|
if Types.RbxId(removedIdOrInstance) then
|
||||||
@@ -78,7 +84,7 @@ local function applyPatch(instanceMap, patch)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local failedToReify = reify(instanceMap, patch.added, id, parentInstance)
|
local failedToReify = reifyInstance(deferredRefs, instanceMap, patch.added, id, parentInstance)
|
||||||
|
|
||||||
if not PatchSet.isEmpty(failedToReify) then
|
if not PatchSet.isEmpty(failedToReify) then
|
||||||
Log.debug("Failed to reify as part of applying a patch: {:#?}", failedToReify)
|
Log.debug("Failed to reify as part of applying a patch: {:#?}", failedToReify)
|
||||||
@@ -143,7 +149,7 @@ local function applyPatch(instanceMap, patch)
|
|||||||
[update.id] = mockVirtualInstance,
|
[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]
|
local newInstance = instanceMap.fromIds[update.id]
|
||||||
|
|
||||||
@@ -206,6 +212,18 @@ local function applyPatch(instanceMap, patch)
|
|||||||
|
|
||||||
if update.changedProperties ~= nil then
|
if update.changedProperties ~= nil then
|
||||||
for propertyName, propertyValue in pairs(update.changedProperties) do
|
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)
|
local decodeSuccess, decodedValue = decodeValue(propertyValue, instanceMap)
|
||||||
if not decodeSuccess then
|
if not decodeSuccess then
|
||||||
unappliedUpdate.changedProperties[propertyName] = propertyValue
|
unappliedUpdate.changedProperties[propertyName] = propertyValue
|
||||||
@@ -230,6 +248,8 @@ local function applyPatch(instanceMap, patch)
|
|||||||
ChangeHistoryService:FinishRecording(historyRecording, Enum.FinishRecordingOperation.Commit)
|
ChangeHistoryService:FinishRecording(historyRecording, Enum.FinishRecordingOperation.Commit)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
return unappliedPatch
|
return unappliedPatch
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,24 @@ local function diff(instanceMap, virtualInstances, rootId)
|
|||||||
|
|
||||||
if getProperySuccess then
|
if getProperySuccess then
|
||||||
local existingValue = existingValueOrErr
|
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 decodeSuccess then
|
||||||
if not trueEquals(existingValue, decodedValue) then
|
if not trueEquals(existingValue, decodedValue) then
|
||||||
|
|||||||
@@ -7,26 +7,6 @@ local PatchSet = require(script.Parent.Parent.PatchSet)
|
|||||||
local setProperty = require(script.Parent.setProperty)
|
local setProperty = require(script.Parent.setProperty)
|
||||||
local decodeValue = require(script.Parent.decodeValue)
|
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
|
Add the given ID and all of its descendants in virtualInstances to the given
|
||||||
PatchSet, marked for addition.
|
PatchSet, marked for addition.
|
||||||
@@ -40,10 +20,21 @@ local function addAllToPatch(patchSet, virtualInstances, id)
|
|||||||
end
|
end
|
||||||
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.
|
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]
|
local virtualInstance = virtualInstances[id]
|
||||||
|
|
||||||
if virtualInstance == nil then
|
if virtualInstance == nil then
|
||||||
@@ -102,7 +93,7 @@ function reifyInner(instanceMap, virtualInstances, id, parentInstance, unapplied
|
|||||||
end
|
end
|
||||||
|
|
||||||
for _, childId in ipairs(virtualInstance.Children) do
|
for _, childId in ipairs(virtualInstance.Children) do
|
||||||
reifyInner(instanceMap, virtualInstances, childId, instance, unappliedPatch, deferredRefs)
|
reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, childId, instance)
|
||||||
end
|
end
|
||||||
|
|
||||||
instance.Parent = parentInstance
|
instance.Parent = parentInstance
|
||||||
@@ -143,6 +134,7 @@ function applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local targetInstance = instanceMap.fromIds[refId]
|
local targetInstance = instanceMap.fromIds[refId]
|
||||||
|
|
||||||
if targetInstance == nil then
|
if targetInstance == nil then
|
||||||
markFailed(entry.id, entry.propertyName, entry.virtualValue)
|
markFailed(entry.id, entry.propertyName, entry.virtualValue)
|
||||||
continue
|
continue
|
||||||
@@ -155,4 +147,7 @@ function applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return reify
|
return {
|
||||||
|
reifyInstance = reifyInstance,
|
||||||
|
applyDeferredRefs = applyDeferredRefs,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
return function()
|
return function()
|
||||||
local reify = require(script.Parent.reify)
|
local reify = require(script.Parent.reify)
|
||||||
|
local reifyInstance, applyDeferredRefs = reify.reifyInstance, reify.applyDeferredRefs
|
||||||
|
|
||||||
local PatchSet = require(script.Parent.Parent.PatchSet)
|
local PatchSet = require(script.Parent.Parent.PatchSet)
|
||||||
local InstanceMap = require(script.Parent.Parent.InstanceMap)
|
local InstanceMap = require(script.Parent.Parent.InstanceMap)
|
||||||
@@ -20,7 +21,11 @@ return function()
|
|||||||
|
|
||||||
it("should throw when given a bogus ID", function()
|
it("should throw when given a bogus ID", function()
|
||||||
expect(function()
|
expect(function()
|
||||||
reify(InstanceMap.new(), {}, "Hi, mom!", game)
|
local deferredRefs = {}
|
||||||
|
local instanceMap = InstanceMap.new()
|
||||||
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, {}, "Hi, mom!", game)
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
end).to.throw()
|
end).to.throw()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -34,8 +39,11 @@ return function()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local deferredRefs = {}
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT", nil)
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT", nil)
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
assert(instanceMap:size() == 0, "expected instanceMap to be empty")
|
assert(instanceMap:size() == 0, "expected instanceMap to be empty")
|
||||||
|
|
||||||
@@ -60,8 +68,11 @@ return function()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local deferredRefs = {}
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
||||||
|
|
||||||
@@ -90,8 +101,11 @@ return function()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local deferredRefs = {}
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
||||||
|
|
||||||
@@ -122,8 +136,11 @@ return function()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local deferredRefs = {}
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
expect(size(unappliedPatch.added)).to.equal(1)
|
expect(size(unappliedPatch.added)).to.equal(1)
|
||||||
expect(unappliedPatch.added["CHILD"]).to.equal(virtualInstances["CHILD"])
|
expect(unappliedPatch.added["CHILD"]).to.equal(virtualInstances["CHILD"])
|
||||||
@@ -153,8 +170,11 @@ return function()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local deferredRefs = {}
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
local instance = instanceMap.fromIds["ROOT"]
|
local instance = instanceMap.fromIds["ROOT"]
|
||||||
expect(instance.ClassName).to.equal("StringValue")
|
expect(instance.ClassName).to.equal("StringValue")
|
||||||
@@ -196,8 +216,11 @@ return function()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local deferredRefs = {}
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
||||||
|
|
||||||
@@ -223,13 +246,16 @@ return function()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local deferredRefs = {}
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
|
|
||||||
local existing = Instance.new("Folder")
|
local existing = Instance.new("Folder")
|
||||||
existing.Name = "Existing"
|
existing.Name = "Existing"
|
||||||
instanceMap:insert("EXISTING", existing)
|
instanceMap:insert("EXISTING", existing)
|
||||||
|
|
||||||
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
||||||
|
|
||||||
@@ -268,8 +294,11 @@ return function()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local deferredRefs = {}
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
||||||
|
|
||||||
@@ -307,8 +336,11 @@ return function()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local deferredRefs = {}
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
|
||||||
|
|
||||||
@@ -332,8 +364,11 @@ return function()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local deferredRefs = {}
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT")
|
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
|
||||||
|
|
||||||
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
|
||||||
assert(not PatchSet.hasRemoves(unappliedPatch), "expected no removes")
|
assert(not PatchSet.hasRemoves(unappliedPatch), "expected no removes")
|
||||||
assert(not PatchSet.hasAdditions(unappliedPatch), "expected no additions")
|
assert(not PatchSet.hasAdditions(unappliedPatch), "expected no additions")
|
||||||
|
|||||||
Reference in New Issue
Block a user