-
Notifications
You must be signed in to change notification settings - Fork 182
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
feat: Update reconciler / reify to use CreateMeshPartAsync to create mesh parts #534
Changes from all commits
2db3c20
bc770de
2d79607
abe69dd
4e19227
29594f1
4f4b8ec
6c1beb9
defed06
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,18 @@ return function() | |
expect(update.changedName).to.equal("We'reGettingToTheCoolPart") | ||
end) | ||
|
||
it("should recreate instance in the update when the instance's ClassName changes", function() | ||
local part = Instance.new("Part") | ||
local properties = { | ||
ClassName = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should set this value to a real |
||
} | ||
|
||
local update = encodePatchUpdate(part, "PART", properties) | ||
|
||
expect(update.changedProperties.ClassName).to.be.ok() | ||
expect(update.requiresRecreate).to.equal(true) | ||
end) | ||
|
||
it("should correctly encode property values", function() | ||
local part = Instance.new("Part") | ||
local properties = { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,10 +93,10 @@ local function applyPatch(instanceMap, patch) | |
} | ||
local partiallyApplied = false | ||
|
||
-- If the instance's className changed, we have a bumpy ride ahead while | ||
-- we recreate this instance and move all of its children into the new | ||
-- version atomically...ish. | ||
if update.changedClassName ~= nil then | ||
-- If the instance's className changed, or there is custom recreate logic, | ||
-- we have a bumpy ride ahead while we recreate this instance and move all | ||
-- of its children into the new version atomically...ish. | ||
if update.requiresRecreate then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are comments in this block of code about its caveats, notably not preserving existing properties if we end up needing to recreate an instance. It's probably not the end of the world, but some of the comments I left about this mostly occurring during Folder<->ModuleScript transitions are probably not accurate anymore. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of having |
||
-- If the instance's name also changed, we'll do it here, since this | ||
-- branch will skip the rest of the loop iteration. | ||
local newName = update.changedName or instance.Name | ||
|
@@ -121,7 +121,7 @@ local function applyPatch(instanceMap, patch) | |
local mockVirtualInstance = { | ||
Id = update.id, | ||
Name = newName, | ||
ClassName = update.changedClassName, | ||
ClassName = update.changedClassName or instance.ClassName, | ||
Properties = newProperties, | ||
Children = {}, | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
return function() | ||
local applyPatch = require(script.Parent.applyPatch) | ||
|
||
local InsertService = game:GetService("InsertService") | ||
|
||
local InstanceMap = require(script.Parent.Parent.InstanceMap) | ||
local PatchSet = require(script.Parent.Parent.PatchSet) | ||
|
||
|
@@ -156,7 +158,33 @@ return function() | |
expect(value.Value).to.equal("WORLD") | ||
end) | ||
|
||
it("should recreate instances when changedClassName is set, preserving children", function() | ||
it("should recreate instances for MeshId updates", function() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a great test! |
||
local mesh = InsertService:CreateMeshPartAsync("rbxassetid://3229650568", Enum.CollisionFidelity.Default, Enum.RenderFidelity.Automatic) | ||
|
||
local instanceMap = InstanceMap.new() | ||
instanceMap:insert("VALUE", mesh) | ||
|
||
local patch = PatchSet.newEmpty() | ||
table.insert(patch.updated, { | ||
id = "VALUE", | ||
changedProperties = { | ||
MeshId = { | ||
Content = "rbxassetid://4868357305", | ||
}, | ||
}, | ||
requiresRecreate = true | ||
}) | ||
|
||
local unapplied = applyPatch(instanceMap, patch) | ||
local newInstance = instanceMap.fromIds["VALUE"] | ||
|
||
expect(mesh.Parent).never.to.be.ok() | ||
expect(mesh).never.to.equal(newInstance) | ||
expect(newInstance).to.be.ok() | ||
expect(newInstance.MeshId).to.equal("rbxassetid://4868357305") | ||
end) | ||
|
||
it("should recreate instances when requiresRecreate is set, preserving children", function() | ||
local root = Instance.new("Folder") | ||
root.Name = "Initial Root Name" | ||
|
||
|
@@ -173,6 +201,7 @@ return function() | |
id = "ROOT", | ||
changedName = "Updated Root Name", | ||
changedClassName = "StringValue", | ||
requiresRecreate = true, | ||
changedProperties = { | ||
Value = { | ||
String = "I am Root", | ||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,6 +2,8 @@ | |||||||||
"Reifies" a virtual DOM, constructing a real DOM with the same shape. | ||||||||||
]] | ||||||||||
|
||||||||||
local InsertService = game:GetService('InsertService') | ||||||||||
|
||||||||||
local invariant = require(script.Parent.Parent.invariant) | ||||||||||
local PatchSet = require(script.Parent.Parent.PatchSet) | ||||||||||
local setProperty = require(script.Parent.setProperty) | ||||||||||
|
@@ -40,6 +42,20 @@ local function addAllToPatch(patchSet, virtualInstances, id) | |||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
local function createInstance(instanceMap, className, properties) | ||||||||||
-- Custom logic for MeshParts | ||||||||||
-- see: https://github.com/rojo-rbx/rojo/issues/402 | ||||||||||
if className == "MeshPart" then | ||||||||||
local okMeshId, meshId = decodeValue(properties.MeshId, instanceMap) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to handle |
||||||||||
|
||||||||||
-- It is okay to provide default properties here, because | ||||||||||
-- they will be overriden on the update cycle | ||||||||||
Comment on lines
+51
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
return InsertService:CreateMeshPartAsync(meshId, Enum.CollisionFidelity.Default, Enum.RenderFidelity.Automatic) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the first time we'll be introducing an async function into Rojo's reconciler bits. This is a possible source of bugs that we should watch out for in the future. |
||||||||||
else | ||||||||||
return Instance.new(className) | ||||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
--[[ | ||||||||||
Inner function that defines the core routine. | ||||||||||
]] | ||||||||||
|
@@ -53,7 +69,7 @@ function reifyInner(instanceMap, virtualInstances, id, parentInstance, unapplied | |||||||||
-- Instance.new can fail if we're passing in something that can't be | ||||||||||
-- created, like a service, something enabled with a feature flag, or | ||||||||||
-- something that requires higher security than we have. | ||||||||||
local ok, instance = pcall(Instance.new, virtualInstance.ClassName) | ||||||||||
local ok, instance = pcall(createInstance, instanceMap, virtualInstance.ClassName, virtualInstance.Properties) | ||||||||||
|
||||||||||
if not ok then | ||||||||||
addAllToPatch(unappliedPatch, virtualInstances, id) | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ local ApiInstanceUpdate = t.interface({ | |
id = RbxId, | ||
changedName = t.optional(t.string), | ||
changedClassName = t.optional(t.string), | ||
requiresRecreate = t.optional(t.boolean), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should update this interface at all. The server generates most of the patches that the Rojo plugin handles and it does not populate |
||
changedProperties = t.map(t.string, ApiValue), | ||
changedMetadata = t.optional(ApiInstanceMetadata), | ||
}) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"name": "mesh_parts", | ||
"tree": { | ||
"$className": "DataModel", | ||
|
||
"Workspace": { | ||
"TwoParts": { | ||
"$path": "src" | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change, whilst structural (it now encodes the Name field as well as setting the changedName property) has not changed the intended logic and is more concise.