mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 14:01:37 +00:00
Use rbx_dom_weak
This commit is contained in:
parent
fb5245e2af
commit
ca813aede0
10 changed files with 125 additions and 154 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
/site
|
/site
|
||||||
/target
|
**/target
|
||||||
/scratch-project
|
/scratch-project
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
/server/failed-snapshots/
|
/server/failed-snapshots/
|
||||||
5
.gitmodules
vendored
5
.gitmodules
vendored
|
|
@ -12,4 +12,7 @@
|
||||||
url = https://github.com/LPGhatguy/roblox-lua-promise.git
|
url = https://github.com/LPGhatguy/roblox-lua-promise.git
|
||||||
[submodule "plugin/modules/t"]
|
[submodule "plugin/modules/t"]
|
||||||
path = plugin/modules/t
|
path = plugin/modules/t
|
||||||
url = https://github.com/osyrisrblx/t.git
|
url = https://github.com/osyrisrblx/t.git
|
||||||
|
[submodule "plugin/modules/rbx-dom"]
|
||||||
|
path = plugin/modules/rbx-dom
|
||||||
|
url = https://github.com/LPGhatguy/rbx-dom
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@
|
||||||
"Roact": {
|
"Roact": {
|
||||||
"$path": "modules/roact/lib"
|
"$path": "modules/roact/lib"
|
||||||
},
|
},
|
||||||
|
"RbxDomLua": {
|
||||||
|
"$path": "modules/rbx-dom/rbx_dom_lua/src"
|
||||||
|
},
|
||||||
"Promise": {
|
"Promise": {
|
||||||
"$path": "modules/promise/lib"
|
"$path": "modules/promise/lib"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
1
plugin/modules/rbx-dom
Submodule
1
plugin/modules/rbx-dom
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 42c620612ce03c4026d3836d9f4c0446410929a7
|
||||||
|
|
@ -15,6 +15,9 @@
|
||||||
"Roact": {
|
"Roact": {
|
||||||
"$path": "modules/roact/lib"
|
"$path": "modules/roact/lib"
|
||||||
},
|
},
|
||||||
|
"RbxDomLua": {
|
||||||
|
"$path": "modules/rbx-dom/rbx_dom_lua/src"
|
||||||
|
},
|
||||||
"Promise": {
|
"Promise": {
|
||||||
"$path": "modules/promise/lib"
|
"$path": "modules/promise/lib"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ local t = require(script.Parent.Parent.t)
|
||||||
|
|
||||||
local InstanceMap = require(script.Parent.InstanceMap)
|
local InstanceMap = require(script.Parent.InstanceMap)
|
||||||
local Logging = require(script.Parent.Logging)
|
local Logging = require(script.Parent.Logging)
|
||||||
local setCanonicalProperty = require(script.Parent.setCanonicalProperty)
|
local RbxDomLua = require(script.Parent.Parent.RbxDomLua)
|
||||||
local rojoValueToRobloxValue = require(script.Parent.rojoValueToRobloxValue)
|
|
||||||
local Types = require(script.Parent.Types)
|
local Types = require(script.Parent.Types)
|
||||||
|
|
||||||
local Reconciler = {}
|
local Reconciler = {}
|
||||||
Reconciler.__index = Reconciler
|
Reconciler.__index = Reconciler
|
||||||
|
|
||||||
|
local writeInstance = RbxDomLua.CanonicalInstance.writeInstance
|
||||||
|
|
||||||
function Reconciler.new()
|
function Reconciler.new()
|
||||||
local self = {
|
local self = {
|
||||||
instanceMap = InstanceMap.new(),
|
instanceMap = InstanceMap.new(),
|
||||||
|
|
@ -27,6 +28,29 @@ function Reconciler:applyUpdate(requestedIds, virtualInstancesById)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Reconciler:mapProperties(virtualProperties, virtualInstancesById)
|
||||||
|
-- Maps properties from the Rojo format to one rbx-dom-lua uses
|
||||||
|
local properties = {}
|
||||||
|
|
||||||
|
for propertyName, property in pairs(virtualProperties) do
|
||||||
|
if property.Type == "BinaryString" then
|
||||||
|
local binaryString = ""
|
||||||
|
for _, characterCode in pairs(property.Value) do
|
||||||
|
binaryString = binaryString .. string.char(characterCode)
|
||||||
|
end
|
||||||
|
properties[propertyName] = binaryString
|
||||||
|
elseif property.Type == "Ref" then
|
||||||
|
if property.Value ~= nil then
|
||||||
|
properties[propertyName] = self.instanceMap.fromIds[property.Value]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
properties[propertyName] = property.Value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return properties
|
||||||
|
end
|
||||||
|
|
||||||
local reconcileSchema = Types.ifEnabled(t.tuple(
|
local reconcileSchema = Types.ifEnabled(t.tuple(
|
||||||
t.map(t.string, Types.VirtualInstance),
|
t.map(t.string, Types.VirtualInstance),
|
||||||
t.string,
|
t.string,
|
||||||
|
|
@ -53,11 +77,9 @@ function Reconciler:reconcile(virtualInstancesById, id, instance)
|
||||||
self.instanceMap:insert(id, instance)
|
self.instanceMap:insert(id, instance)
|
||||||
|
|
||||||
-- Some instances don't like being named, even if their name already matches
|
-- Some instances don't like being named, even if their name already matches
|
||||||
setCanonicalProperty(instance, "Name", virtualInstance.Name)
|
writeInstance(instance, {
|
||||||
|
Name = virtualInstance.Name,
|
||||||
for key, value in pairs(virtualInstance.Properties) do
|
})
|
||||||
setCanonicalProperty(instance, key, rojoValueToRobloxValue(value))
|
|
||||||
end
|
|
||||||
|
|
||||||
local existingChildren = instance:GetChildren()
|
local existingChildren = instance:GetChildren()
|
||||||
|
|
||||||
|
|
@ -105,6 +127,8 @@ function Reconciler:reconcile(virtualInstancesById, id, instance)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
writeInstance(instance, self:mapProperties(virtualInstance.Properties))
|
||||||
|
|
||||||
-- The root instance of a project won't have a parent, like the DataModel,
|
-- The root instance of a project won't have a parent, like the DataModel,
|
||||||
-- so we need to be careful here.
|
-- so we need to be careful here.
|
||||||
if virtualInstance.Parent ~= nil then
|
if virtualInstance.Parent ~= nil then
|
||||||
|
|
@ -117,7 +141,9 @@ function Reconciler:reconcile(virtualInstancesById, id, instance)
|
||||||
|
|
||||||
-- Some instances, like services, don't like having their Parent
|
-- Some instances, like services, don't like having their Parent
|
||||||
-- property poked, even if we're setting it to the same value.
|
-- property poked, even if we're setting it to the same value.
|
||||||
setCanonicalProperty(instance, "Parent", parent)
|
writeInstance(instance, {
|
||||||
|
Parent = parent,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
@ -143,18 +169,19 @@ function Reconciler:__reify(virtualInstancesById, id, parent)
|
||||||
local virtualInstance = virtualInstancesById[id]
|
local virtualInstance = virtualInstancesById[id]
|
||||||
|
|
||||||
local instance = Instance.new(virtualInstance.ClassName)
|
local instance = Instance.new(virtualInstance.ClassName)
|
||||||
|
writeInstance(instance, {
|
||||||
for key, value in pairs(virtualInstance.Properties) do
|
Name = virtualInstance.Name,
|
||||||
setCanonicalProperty(instance, key, rojoValueToRobloxValue(value))
|
})
|
||||||
end
|
|
||||||
|
|
||||||
setCanonicalProperty(instance, "Name", virtualInstance.Name)
|
|
||||||
|
|
||||||
for _, childId in ipairs(virtualInstance.Children) do
|
for _, childId in ipairs(virtualInstance.Children) do
|
||||||
self:__reify(virtualInstancesById, childId, instance)
|
self:__reify(virtualInstancesById, childId, instance)
|
||||||
end
|
end
|
||||||
|
|
||||||
setCanonicalProperty(instance, "Parent", parent)
|
writeInstance(instance, self:mapProperties(virtualInstance.Properties))
|
||||||
|
writeInstance(instance, {
|
||||||
|
Parent = parent,
|
||||||
|
})
|
||||||
|
|
||||||
self.instanceMap:insert(id, instance)
|
self.instanceMap:insert(id, instance)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
local CollectionService = game:GetService("CollectionService")
|
||||||
|
|
||||||
local Reconciler = require(script.Parent.Reconciler)
|
local Reconciler = require(script.Parent.Reconciler)
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
|
|
@ -53,8 +55,8 @@ return function()
|
||||||
Properties = {
|
Properties = {
|
||||||
Value = {
|
Value = {
|
||||||
Type = "Int32",
|
Type = "Int32",
|
||||||
Value = 9
|
Value = 9,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -65,6 +67,73 @@ return function()
|
||||||
expect(instance.Value).to.equal(9)
|
expect(instance.Value).to.equal(9)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("should assign properties that can't just be easily set", function()
|
||||||
|
local instance = Instance.new("Folder")
|
||||||
|
|
||||||
|
local binaryString = {}
|
||||||
|
for _, character in pairs(("Tag1\0Tag2"):split("")) do
|
||||||
|
table.insert(binaryString, character:byte())
|
||||||
|
end
|
||||||
|
|
||||||
|
local instanceId = "test-id"
|
||||||
|
local virtualInstancesById = {
|
||||||
|
[instanceId] = {
|
||||||
|
Name = "Folder",
|
||||||
|
ClassName = "Folder",
|
||||||
|
Children = {},
|
||||||
|
Properties = {
|
||||||
|
Tags = {
|
||||||
|
Type = "BinaryString",
|
||||||
|
Value = binaryString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local reconciler = Reconciler.new()
|
||||||
|
reconciler:reconcile(virtualInstancesById, instanceId, instance)
|
||||||
|
|
||||||
|
assert(CollectionService:HasTag(instance, "Tag1"))
|
||||||
|
assert(CollectionService:HasTag(instance, "Tag2"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should assign ref properties", function()
|
||||||
|
local model = Instance.new("Model")
|
||||||
|
model.Name = "Parent"
|
||||||
|
|
||||||
|
local child = Instance.new("Part")
|
||||||
|
child.Name = "Child"
|
||||||
|
child.Parent = model
|
||||||
|
|
||||||
|
local childId = "child-id"
|
||||||
|
local modelId = "model-id"
|
||||||
|
|
||||||
|
local virtualInstancesById = {
|
||||||
|
[modelId] = {
|
||||||
|
Name = "Parent",
|
||||||
|
ClassName = "Model",
|
||||||
|
Children = { childId },
|
||||||
|
Properties = {
|
||||||
|
PrimaryPart = {
|
||||||
|
Type = "Ref",
|
||||||
|
Value = childId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[childId] = {
|
||||||
|
Name = "Child",
|
||||||
|
ClassName = "Part",
|
||||||
|
Children = {},
|
||||||
|
Properties = {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local reconciler = Reconciler.new()
|
||||||
|
reconciler:reconcile(virtualInstancesById, modelId, model)
|
||||||
|
expect(model.PrimaryPart).to.equal(child)
|
||||||
|
end)
|
||||||
|
|
||||||
it("should wipe unknown children by default", function()
|
it("should wipe unknown children by default", function()
|
||||||
local parent = Instance.new("Folder")
|
local parent = Instance.new("Folder")
|
||||||
parent.Name = "Parent"
|
parent.Name = "Parent"
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
local primitiveTypes = {
|
|
||||||
Bool = true,
|
|
||||||
Enum = true,
|
|
||||||
Float32 = true,
|
|
||||||
Float64 = true,
|
|
||||||
Int32 = true,
|
|
||||||
Int64 = true,
|
|
||||||
String = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
local directConstructors = {
|
|
||||||
CFrame = CFrame.new,
|
|
||||||
Color3 = Color3.new,
|
|
||||||
Color3uint8 = Color3.fromRGB,
|
|
||||||
Rect = Rect.new,
|
|
||||||
UDim = UDim.new,
|
|
||||||
UDim2 = UDim2.new,
|
|
||||||
Vector2 = Vector2.new,
|
|
||||||
Vector2int16 = Vector2int16.new,
|
|
||||||
Vector3 = Vector3.new,
|
|
||||||
Vector3int16 = Vector3int16.new,
|
|
||||||
}
|
|
||||||
|
|
||||||
local function rojoValueToRobloxValue(value)
|
|
||||||
if primitiveTypes[value.Type] then
|
|
||||||
return value.Value
|
|
||||||
end
|
|
||||||
|
|
||||||
local constructor = directConstructors[value.Type]
|
|
||||||
if constructor ~= nil then
|
|
||||||
return constructor(unpack(value.Value))
|
|
||||||
end
|
|
||||||
|
|
||||||
local errorMessage = ("The Rojo plugin doesn't know how to handle values of type %q yet!"):format(tostring(value.Type))
|
|
||||||
error(errorMessage)
|
|
||||||
end
|
|
||||||
|
|
||||||
return rojoValueToRobloxValue
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
local rojoValueToRobloxValue = require(script.Parent.rojoValueToRobloxValue)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
it("should convert primitives", function()
|
|
||||||
local inputString = {
|
|
||||||
Type = "String",
|
|
||||||
Value = "Hello, world!",
|
|
||||||
}
|
|
||||||
|
|
||||||
local inputFloat32 = {
|
|
||||||
Type = "Float32",
|
|
||||||
Value = 12341.512,
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(rojoValueToRobloxValue(inputString)).to.equal(inputString.Value)
|
|
||||||
expect(rojoValueToRobloxValue(inputFloat32)).to.equal(inputFloat32.Value)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("should convert properties with direct constructors", function()
|
|
||||||
local inputColor3 = {
|
|
||||||
Type = "Color3",
|
|
||||||
Value = {0, 1, 0.5},
|
|
||||||
}
|
|
||||||
local outputColor3 = Color3.new(0, 1, 0.5)
|
|
||||||
|
|
||||||
local inputCFrame = {
|
|
||||||
Type = "CFrame",
|
|
||||||
Value = {
|
|
||||||
1, 2, 3,
|
|
||||||
4, 5, 6,
|
|
||||||
7, 8, 9,
|
|
||||||
10, 11, 12,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
local outputCFrame = CFrame.new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
|
|
||||||
|
|
||||||
expect(rojoValueToRobloxValue(inputColor3)).to.equal(outputColor3)
|
|
||||||
expect(rojoValueToRobloxValue(inputCFrame)).to.equal(outputCFrame)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
local Logging = require(script.Parent.Logging)
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Attempts to set a property on the given instance.
|
|
||||||
|
|
||||||
This method deals in terms of what Rojo calls 'canonical properties', which
|
|
||||||
don't necessarily exist either in serialization or in Lua-reflected APIs,
|
|
||||||
but may be present in the API dump.
|
|
||||||
|
|
||||||
Ideally, canonical properties map 1:1 with properties we can assign, but in
|
|
||||||
some cases like LocalizationTable contents and CollectionService tags, we
|
|
||||||
have to read/write properties a little differently.
|
|
||||||
]]
|
|
||||||
local function setCanonicalProperty(instance, key, value)
|
|
||||||
-- The 'Contents' property of LocalizationTable isn't directly exposed, but
|
|
||||||
-- has corresponding (deprecated) getters and setters.
|
|
||||||
if instance.ClassName == "LocalizationTable" and key == "Contents" then
|
|
||||||
instance:SetContents(value)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Temporary workaround for fixing issue #141 in this specific case.
|
|
||||||
if instance.ClassName == "Lighting" and key == "Technology" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If we don't have permissions to access this value at all, we can skip it.
|
|
||||||
local readSuccess, existingValue = pcall(function()
|
|
||||||
return instance[key]
|
|
||||||
end)
|
|
||||||
|
|
||||||
if not readSuccess then
|
|
||||||
-- An error will be thrown if there was a permission issue or if the
|
|
||||||
-- property doesn't exist. In the latter case, we should tell the user
|
|
||||||
-- because it's probably their fault.
|
|
||||||
if existingValue:find("lacking permission") then
|
|
||||||
Logging.trace("Permission error reading property %s on class %s", tostring(key), instance.ClassName)
|
|
||||||
return
|
|
||||||
else
|
|
||||||
error(("Invalid property %s on class %s: %s"):format(tostring(key), instance.ClassName, existingValue), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local writeSuccess, err = pcall(function()
|
|
||||||
if existingValue ~= value then
|
|
||||||
instance[key] = value
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
if not writeSuccess then
|
|
||||||
error(("Cannot set property %s on class %s: %s"):format(tostring(key), instance.ClassName, err), 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
return setCanonicalProperty
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue