mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-20 17:41:37 +00:00
Upgrade to rbx_dom_weak 2.0 (#377)
* Mostly mechanical port bits * Almost there * It builds again! * Turn on all the code again * Tests compiling but not passing * Stub work for value resolution * Implement resolution minus enums and derived properties * Implement property descriptor resolution * Update referent snapshots * Update unions test project Using a place file instead of a model yields better error messages in Roblox Studio. * Add easy shortcut to testing with local rbx-dom * Update rbx-dom * Add enum resolution * Update init.meta.json to use UnresolvedValue * Expand value resolution support, add test * Filter SharedString values from web API * Add 'property' builder method to InstanceSnapshot * Change InstanceSnapshot/InstanceBuilder boundary * Fix remove_file crash * rustfmt * Update to latest rbx_dom_lua * Update dependencies, including rbx_dom_weak * Update to latest rbx-dom * Update dependencies * Update rbx-dom, fixing more bugs * Remove experimental warning on binary place builds * Remove unused imports
This commit is contained in:
parent
b84aab0960
commit
59ef5f05ea
63 changed files with 45602 additions and 21004 deletions
578
Cargo.lock
generated
578
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
17
Cargo.toml
17
Cargo.toml
|
|
@ -47,6 +47,19 @@ harness = false
|
|||
[dependencies]
|
||||
memofs = { version = "0.1.2", path = "memofs" }
|
||||
|
||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
||||
# rbx_binary = { path = "../rbx-dom/rbx_binary" }
|
||||
# rbx_dom_weak = { path = "../rbx-dom/rbx_dom_weak" }
|
||||
# rbx_reflection = { path = "../rbx-dom/rbx_reflection" }
|
||||
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
||||
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
||||
|
||||
rbx_binary = { git = "https://github.com/rojo-rbx/rbx-dom", branch = "master" }
|
||||
rbx_dom_weak = { git = "https://github.com/rojo-rbx/rbx-dom", branch = "master" }
|
||||
rbx_reflection = { git = "https://github.com/rojo-rbx/rbx-dom", branch = "master" }
|
||||
rbx_reflection_database = { git = "https://github.com/rojo-rbx/rbx-dom", branch = "master" }
|
||||
rbx_xml = { git = "https://github.com/rojo-rbx/rbx-dom", branch = "master" }
|
||||
|
||||
anyhow = "1.0.27"
|
||||
backtrace = "0.3"
|
||||
bincode = "1.2.1"
|
||||
|
|
@ -64,10 +77,6 @@ log = "0.4.8"
|
|||
maplit = "1.0.1"
|
||||
notify = "4.0.14"
|
||||
opener = "0.4.1"
|
||||
rbx_binary = "0.5.0"
|
||||
rbx_dom_weak = "1.10.1"
|
||||
rbx_reflection = "3.3.408"
|
||||
rbx_xml = "0.11.3"
|
||||
regex = "1.3.1"
|
||||
reqwest = "0.9.20"
|
||||
ritz = "0.1.0"
|
||||
|
|
|
|||
1
plugin/rbx_dom_lua/run-tests.lua
Normal file
1
plugin/rbx_dom_lua/run-tests.lua
Normal file
|
|
@ -0,0 +1 @@
|
|||
require(game.ReplicatedStorage.TestEZ).TestBootstrap:run({game.ReplicatedStorage.RbxDom})
|
||||
|
|
@ -20,6 +20,9 @@ local function serializeFloat(value)
|
|||
return value
|
||||
end
|
||||
|
||||
local ALL_AXES = {"X", "Y", "Z"}
|
||||
local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"}
|
||||
|
||||
local encoders
|
||||
encoders = {
|
||||
Bool = identity,
|
||||
|
|
@ -33,12 +36,56 @@ encoders = {
|
|||
BinaryString = base64.encode,
|
||||
SharedString = base64.encode,
|
||||
|
||||
Axes = function(value)
|
||||
local output = {}
|
||||
|
||||
for _, axis in ipairs(ALL_AXES) do
|
||||
if value[axis] then
|
||||
table.insert(output, axis)
|
||||
end
|
||||
end
|
||||
|
||||
return output
|
||||
end,
|
||||
|
||||
Faces = function(value)
|
||||
local output = {}
|
||||
|
||||
for _, face in ipairs(ALL_FACES) do
|
||||
if value[face] then
|
||||
table.insert(output, face)
|
||||
end
|
||||
end
|
||||
|
||||
return output
|
||||
end,
|
||||
|
||||
Enum = function(value)
|
||||
if typeof(value) == "number" then
|
||||
return value
|
||||
else
|
||||
return value.Value
|
||||
end
|
||||
end,
|
||||
|
||||
BrickColor = function(value)
|
||||
return value.Number
|
||||
end,
|
||||
|
||||
CFrame = function(value)
|
||||
return {value:GetComponents()}
|
||||
local x, y, z,
|
||||
r00, r01, r02,
|
||||
r10, r11, r12,
|
||||
r20, r21, r22 = value:GetComponents()
|
||||
|
||||
return {
|
||||
Position = {x, y, z},
|
||||
Orientation = {
|
||||
{r00, r10, r20},
|
||||
{r01, r11, r21},
|
||||
{r02, r12, r22},
|
||||
},
|
||||
}
|
||||
end,
|
||||
Color3 = function(value)
|
||||
return {value.r, value.g, value.b}
|
||||
|
|
@ -77,15 +124,18 @@ encoders = {
|
|||
end,
|
||||
Rect = function(value)
|
||||
return {
|
||||
Min = {value.Min.X, value.Min.Y},
|
||||
Max = {value.Max.X, value.Max.Y},
|
||||
encoders.Vector2(value.Min),
|
||||
encoders.Vector2(value.Max),
|
||||
}
|
||||
end,
|
||||
UDim = function(value)
|
||||
return {value.Scale, value.Offset}
|
||||
end,
|
||||
UDim2 = function(value)
|
||||
return {value.X.Scale, value.X.Offset, value.Y.Scale, value.Y.Offset}
|
||||
return {
|
||||
encoders.UDim(value.X),
|
||||
encoders.UDim(value.Y),
|
||||
}
|
||||
end,
|
||||
Vector2 = function(value)
|
||||
return {
|
||||
|
|
@ -121,12 +171,35 @@ encoders = {
|
|||
end
|
||||
end,
|
||||
|
||||
Ray = function(value)
|
||||
return {
|
||||
Origin = encoders.Vector3(value.Origin),
|
||||
Direction = encoders.Vector3(value.Direction),
|
||||
}
|
||||
end,
|
||||
|
||||
Ref = function(value)
|
||||
return nil
|
||||
end,
|
||||
|
||||
Region3int16 = function(value)
|
||||
return {
|
||||
encoders.Vector3int16(value.Min),
|
||||
encoders.Vector3int16(value.Max),
|
||||
}
|
||||
end,
|
||||
|
||||
Color3uint8 = function(value)
|
||||
return {
|
||||
math.round(value.R * 255),
|
||||
math.round(value.G * 255),
|
||||
math.round(value.B * 255),
|
||||
}
|
||||
end,
|
||||
}
|
||||
|
||||
local decoders = {
|
||||
local decoders
|
||||
decoders = {
|
||||
Bool = identity,
|
||||
Content = identity,
|
||||
Enum = identity,
|
||||
|
|
@ -141,19 +214,63 @@ local decoders = {
|
|||
|
||||
BrickColor = BrickColor.new,
|
||||
|
||||
CFrame = unpackDecoder(CFrame.new),
|
||||
Color3 = unpackDecoder(Color3.new),
|
||||
Color3uint8 = unpackDecoder(Color3.fromRGB),
|
||||
NumberRange = unpackDecoder(NumberRange.new),
|
||||
UDim = unpackDecoder(UDim.new),
|
||||
UDim2 = unpackDecoder(UDim2.new),
|
||||
Vector2 = unpackDecoder(Vector2.new),
|
||||
Vector2int16 = unpackDecoder(Vector2int16.new),
|
||||
Vector3 = unpackDecoder(Vector3.new),
|
||||
Vector3int16 = unpackDecoder(Vector3int16.new),
|
||||
|
||||
UDim2 = function(value)
|
||||
return UDim2.new(
|
||||
value[1][1],
|
||||
value[1][2],
|
||||
value[2][1],
|
||||
value[2][2]
|
||||
)
|
||||
end,
|
||||
|
||||
Axes = function(value)
|
||||
local axes = {}
|
||||
for index, axisName in ipairs(value) do
|
||||
axes[index] = Enum.Axis[axisName]
|
||||
end
|
||||
|
||||
return Axes.new(unpack(axes))
|
||||
end,
|
||||
|
||||
Faces = function(value)
|
||||
local normalIds = {}
|
||||
for index, faceName in ipairs(value) do
|
||||
normalIds[index] = Enum.NormalId[faceName]
|
||||
end
|
||||
|
||||
return Faces.new(unpack(normalIds))
|
||||
end,
|
||||
|
||||
CFrame = function(value)
|
||||
return CFrame.fromMatrix(
|
||||
decoders.Vector3(value.Position),
|
||||
decoders.Vector3(value.Orientation[1]),
|
||||
decoders.Vector3(value.Orientation[2]),
|
||||
decoders.Vector3(value.Orientation[3])
|
||||
)
|
||||
end,
|
||||
|
||||
Rect = function(value)
|
||||
return Rect.new(value.Min[1], value.Min[2], value.Max[1], value.Max[2])
|
||||
return Rect.new(
|
||||
decoders.Vector2(value[1]),
|
||||
decoders.Vector2(value[2])
|
||||
)
|
||||
end,
|
||||
|
||||
Ray = function(value)
|
||||
return Ray.new(
|
||||
decoders.Vector3(value.Origin),
|
||||
decoders.Vector3(value.Direction)
|
||||
)
|
||||
end,
|
||||
|
||||
NumberSequence = function(value)
|
||||
|
|
@ -200,6 +317,13 @@ local decoders = {
|
|||
Ref = function()
|
||||
return nil
|
||||
end,
|
||||
|
||||
Region3int16 = function(value)
|
||||
return Region3int16.new(
|
||||
decoders.Vector3int16(value[1]),
|
||||
decoders.Vector3int16(value[2])
|
||||
)
|
||||
end,
|
||||
}
|
||||
|
||||
local EncodedValue = {}
|
||||
|
|
@ -216,27 +340,16 @@ end
|
|||
function EncodedValue.encode(rbxValue, propertyType)
|
||||
assert(propertyType ~= nil, "Property type descriptor is required")
|
||||
|
||||
if propertyType.type == "Data" then
|
||||
local encoder = encoders[propertyType.name]
|
||||
local encoder = encoders[propertyType]
|
||||
|
||||
if encoder == nil then
|
||||
return false, ("Missing encoder for property type %q"):format(propertyType.name)
|
||||
end
|
||||
|
||||
if encoder ~= nil then
|
||||
return true, {
|
||||
Type = propertyType.name,
|
||||
Value = encoder(rbxValue),
|
||||
}
|
||||
end
|
||||
elseif propertyType.type == "Enum" then
|
||||
return true, {
|
||||
Type = "Enum",
|
||||
Value = rbxValue.Value,
|
||||
}
|
||||
if encoder == nil then
|
||||
return false, ("Missing encoder for property type %q"):format(propertyType)
|
||||
end
|
||||
|
||||
return false, ("Unknown property descriptor type %q"):format(tostring(propertyType.type))
|
||||
return true, {
|
||||
Type = propertyType,
|
||||
Value = encoder(rbxValue),
|
||||
}
|
||||
end
|
||||
|
||||
return EncodedValue
|
||||
return EncodedValue
|
||||
|
|
|
|||
|
|
@ -1,127 +1,63 @@
|
|||
return function()
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local RbxDom = require(script.Parent)
|
||||
local EncodedValue = require(script.Parent.EncodedValue)
|
||||
local allValues = require(script.Parent.allValues)
|
||||
|
||||
it("should decode Rect values", function()
|
||||
local input = {
|
||||
Type = "Rect",
|
||||
Value = {
|
||||
Min = {1, 2},
|
||||
Max = {3, 4},
|
||||
},
|
||||
}
|
||||
local function deepEq(a, b)
|
||||
if typeof(a) ~= typeof(b) then
|
||||
return false
|
||||
end
|
||||
|
||||
local output = Rect.new(1, 2, 3, 4)
|
||||
local ty = typeof(a)
|
||||
|
||||
local ok, decoded = EncodedValue.decode(input)
|
||||
if ty == "table" then
|
||||
local visited = {}
|
||||
|
||||
for key, valueA in pairs(a) do
|
||||
visited[key] = true
|
||||
|
||||
if not deepEq(valueA, b[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
assert(ok, decoded)
|
||||
expect(decoded).to.equal(output)
|
||||
end)
|
||||
for key, valueB in pairs(b) do
|
||||
if visited[key] then
|
||||
continue
|
||||
end
|
||||
|
||||
it("should decode ColorSequence values", function()
|
||||
local input = {
|
||||
Type = "ColorSequence",
|
||||
Value = {
|
||||
Keypoints = {
|
||||
{
|
||||
Time = 0,
|
||||
Color = { 0.12, 0.34, 0.56 },
|
||||
},
|
||||
if not deepEq(valueB, a[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
Time = 1,
|
||||
Color = { 0.13, 0.33, 0.37 },
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
return true
|
||||
else
|
||||
return a == b
|
||||
end
|
||||
end
|
||||
|
||||
local output = ColorSequence.new({
|
||||
ColorSequenceKeypoint.new(0, Color3.new(0.12, 0.34, 0.56)),
|
||||
ColorSequenceKeypoint.new(1, Color3.new(0.13, 0.33, 0.37)),
|
||||
})
|
||||
for testName, testEntry in pairs(allValues) do
|
||||
it("round trip " .. testName, function()
|
||||
local ok, decoded = EncodedValue.decode(testEntry.value)
|
||||
assert(ok, decoded)
|
||||
|
||||
local ok, decoded = EncodedValue.decode(input)
|
||||
assert(ok, decoded)
|
||||
expect(decoded).to.equal(output)
|
||||
end)
|
||||
local ok, encoded = EncodedValue.encode(decoded, testEntry.ty)
|
||||
assert(ok, encoded)
|
||||
|
||||
it("should decode NumberSequence values", function()
|
||||
local input = {
|
||||
Type = "NumberSequence",
|
||||
Value = {
|
||||
Keypoints = {
|
||||
{
|
||||
Time = 0,
|
||||
Value = 0.5,
|
||||
Envelope = 0,
|
||||
},
|
||||
if not deepEq(encoded, testEntry.value) then
|
||||
local expected = HttpService:JSONEncode(testEntry.value)
|
||||
local actual = HttpService:JSONEncode(encoded)
|
||||
|
||||
{
|
||||
Time = 1,
|
||||
Value = 0.5,
|
||||
Envelope = 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
local message = string.format(
|
||||
"Round-trip results did not match.\nExpected:\n%s\nActual:\n%s",
|
||||
expected, actual
|
||||
)
|
||||
|
||||
local output = NumberSequence.new({
|
||||
NumberSequenceKeypoint.new(0, 0.5, 0),
|
||||
NumberSequenceKeypoint.new(1, 0.5, 0),
|
||||
})
|
||||
|
||||
local ok, decoded = EncodedValue.decode(input)
|
||||
assert(ok, decoded)
|
||||
expect(decoded).to.equal(output)
|
||||
end)
|
||||
|
||||
it("should decode PhysicalProperties values", function()
|
||||
local input = {
|
||||
Type = "PhysicalProperties",
|
||||
Value = {
|
||||
Density = 0.1,
|
||||
Friction = 0.2,
|
||||
Elasticity = 0.3,
|
||||
FrictionWeight = 0.4,
|
||||
ElasticityWeight = 0.5,
|
||||
},
|
||||
}
|
||||
|
||||
local output = PhysicalProperties.new(
|
||||
0.1,
|
||||
0.2,
|
||||
0.3,
|
||||
0.4,
|
||||
0.5
|
||||
)
|
||||
|
||||
local ok, decoded = EncodedValue.decode(input)
|
||||
assert(ok, decoded)
|
||||
expect(decoded).to.equal(output)
|
||||
end)
|
||||
|
||||
-- This part of rbx_dom_lua needs some work still.
|
||||
itSKIP("should encode Rect values", function()
|
||||
local input = Rect.new(10, 20, 30, 40)
|
||||
|
||||
local output = {
|
||||
Type = "Rect",
|
||||
Value = {
|
||||
Min = {10, 20},
|
||||
Max = {30, 40},
|
||||
},
|
||||
}
|
||||
|
||||
local descriptor = RbxDom.findCanonicalPropertyDescriptor("ImageLabel", "SliceCenter")
|
||||
local ok, encoded = EncodedValue.encode(input, descriptor)
|
||||
|
||||
assert(ok, encoded)
|
||||
expect(encoded.Type).to.equal(output.Type)
|
||||
expect(encoded.Value.Min[1]).to.equal(output.Value.Min[1])
|
||||
expect(encoded.Value.Min[2]).to.equal(output.Value.Min[2])
|
||||
expect(encoded.Value.Max[1]).to.equal(output.Value.Max[1])
|
||||
expect(encoded.Value.Max[2]).to.equal(output.Value.Max[2])
|
||||
end)
|
||||
end
|
||||
error(message)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ end
|
|||
|
||||
function PropertyDescriptor.fromRaw(data, className, propertyName)
|
||||
return setmetatable({
|
||||
scriptability = data.scriptability,
|
||||
scriptability = data.Scriptability,
|
||||
className = className,
|
||||
name = propertyName,
|
||||
}, PropertyDescriptor)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +0,0 @@
|
|||
return {
|
||||
classes = require(script.classes)
|
||||
}
|
||||
338
plugin/rbx_dom_lua/src/allValues.json
Normal file
338
plugin/rbx_dom_lua/src/allValues.json
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
{
|
||||
"Color3uint8": {
|
||||
"value": {
|
||||
"Type": "Color3uint8",
|
||||
"Value": [
|
||||
0,
|
||||
128,
|
||||
255
|
||||
]
|
||||
},
|
||||
"ty": "Color3uint8"
|
||||
},
|
||||
"Bool": {
|
||||
"value": {
|
||||
"Type": "Bool",
|
||||
"Value": true
|
||||
},
|
||||
"ty": "Bool"
|
||||
},
|
||||
"BinaryString": {
|
||||
"value": {
|
||||
"Type": "BinaryString",
|
||||
"Value": "SGVsbG8h"
|
||||
},
|
||||
"ty": "BinaryString"
|
||||
},
|
||||
"BrickColor": {
|
||||
"value": {
|
||||
"Type": "BrickColor",
|
||||
"Value": 1004
|
||||
},
|
||||
"ty": "BrickColor"
|
||||
},
|
||||
"Vector3": {
|
||||
"value": {
|
||||
"Type": "Vector3",
|
||||
"Value": [
|
||||
-300.0,
|
||||
0.0,
|
||||
1500.0
|
||||
]
|
||||
},
|
||||
"ty": "Vector3"
|
||||
},
|
||||
"String": {
|
||||
"value": {
|
||||
"Type": "String",
|
||||
"Value": "Hello, world!"
|
||||
},
|
||||
"ty": "String"
|
||||
},
|
||||
"NumberRange": {
|
||||
"value": {
|
||||
"Type": "NumberRange",
|
||||
"Value": [
|
||||
-36.0,
|
||||
94.0
|
||||
]
|
||||
},
|
||||
"ty": "NumberRange"
|
||||
},
|
||||
"Int64": {
|
||||
"value": {
|
||||
"Type": "Int64",
|
||||
"Value": 23491023
|
||||
},
|
||||
"ty": "Int64"
|
||||
},
|
||||
"Vector3int16": {
|
||||
"value": {
|
||||
"Type": "Vector3int16",
|
||||
"Value": [
|
||||
60,
|
||||
37,
|
||||
-450
|
||||
]
|
||||
},
|
||||
"ty": "Vector3int16"
|
||||
},
|
||||
"Float32": {
|
||||
"value": {
|
||||
"Type": "Float32",
|
||||
"Value": 15.0
|
||||
},
|
||||
"ty": "Float32"
|
||||
},
|
||||
"NumberSequence": {
|
||||
"value": {
|
||||
"Type": "NumberSequence",
|
||||
"Value": {
|
||||
"Keypoints": [
|
||||
{
|
||||
"Time": 0.0,
|
||||
"Value": 5.0,
|
||||
"Envelope": 2.0
|
||||
},
|
||||
{
|
||||
"Time": 1.0,
|
||||
"Value": 22.0,
|
||||
"Envelope": 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "NumberSequence"
|
||||
},
|
||||
"ColorSequence": {
|
||||
"value": {
|
||||
"Type": "ColorSequence",
|
||||
"Value": {
|
||||
"Keypoints": [
|
||||
{
|
||||
"Time": 0.0,
|
||||
"Color": [
|
||||
1.0,
|
||||
1.0,
|
||||
0.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"Time": 1.0,
|
||||
"Color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "ColorSequence"
|
||||
},
|
||||
"Ray": {
|
||||
"value": {
|
||||
"Type": "Ray",
|
||||
"Value": {
|
||||
"Origin": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"Direction": [
|
||||
4.0,
|
||||
5.0,
|
||||
6.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "Ray"
|
||||
},
|
||||
"Int32": {
|
||||
"value": {
|
||||
"Type": "Int32",
|
||||
"Value": 6014
|
||||
},
|
||||
"ty": "Int32"
|
||||
},
|
||||
"Axes": {
|
||||
"value": {
|
||||
"Type": "Axes",
|
||||
"Value": [
|
||||
"X",
|
||||
"Y",
|
||||
"Z"
|
||||
]
|
||||
},
|
||||
"ty": "Axes"
|
||||
},
|
||||
"Enum": {
|
||||
"value": {
|
||||
"Type": "Enum",
|
||||
"Value": 1234
|
||||
},
|
||||
"ty": "Enum"
|
||||
},
|
||||
"Faces": {
|
||||
"value": {
|
||||
"Type": "Faces",
|
||||
"Value": [
|
||||
"Right",
|
||||
"Top",
|
||||
"Back",
|
||||
"Left",
|
||||
"Bottom",
|
||||
"Front"
|
||||
]
|
||||
},
|
||||
"ty": "Faces"
|
||||
},
|
||||
"UDim": {
|
||||
"value": {
|
||||
"Type": "UDim",
|
||||
"Value": [
|
||||
1.0,
|
||||
32
|
||||
]
|
||||
},
|
||||
"ty": "UDim"
|
||||
},
|
||||
"Vector2": {
|
||||
"value": {
|
||||
"Type": "Vector2",
|
||||
"Value": [
|
||||
-50.0,
|
||||
50.0
|
||||
]
|
||||
},
|
||||
"ty": "Vector2"
|
||||
},
|
||||
"Color3": {
|
||||
"value": {
|
||||
"Type": "Color3",
|
||||
"Value": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
]
|
||||
},
|
||||
"ty": "Color3"
|
||||
},
|
||||
"Float64": {
|
||||
"value": {
|
||||
"Type": "Float64",
|
||||
"Value": 15123.0
|
||||
},
|
||||
"ty": "Float64"
|
||||
},
|
||||
"UDim2": {
|
||||
"value": {
|
||||
"Type": "UDim2",
|
||||
"Value": [
|
||||
[
|
||||
-1.0,
|
||||
100
|
||||
],
|
||||
[
|
||||
1.0,
|
||||
-100
|
||||
]
|
||||
]
|
||||
},
|
||||
"ty": "UDim2"
|
||||
},
|
||||
"Region3int16": {
|
||||
"value": {
|
||||
"Type": "Region3int16",
|
||||
"Value": [
|
||||
[
|
||||
-10,
|
||||
-5,
|
||||
0
|
||||
],
|
||||
[
|
||||
5,
|
||||
10,
|
||||
15
|
||||
]
|
||||
]
|
||||
},
|
||||
"ty": "Region3int16"
|
||||
},
|
||||
"CFrame": {
|
||||
"value": {
|
||||
"Type": "CFrame",
|
||||
"Value": {
|
||||
"Position": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"Orientation": [
|
||||
[
|
||||
4.0,
|
||||
5.0,
|
||||
6.0
|
||||
],
|
||||
[
|
||||
7.0,
|
||||
8.0,
|
||||
9.0
|
||||
],
|
||||
[
|
||||
10.0,
|
||||
11.0,
|
||||
12.0
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "CFrame"
|
||||
},
|
||||
"Content": {
|
||||
"value": {
|
||||
"Type": "Content",
|
||||
"Value": "rbxassetid://12345"
|
||||
},
|
||||
"ty": "Content"
|
||||
},
|
||||
"PhysicalProperties": {
|
||||
"value": {
|
||||
"Type": "PhysicalProperties",
|
||||
"Value": {
|
||||
"Density": 0.5,
|
||||
"Friction": 1.0,
|
||||
"Elasticity": 0.0,
|
||||
"FrictionWeight": 50.0,
|
||||
"ElasticityWeight": 25.0
|
||||
}
|
||||
},
|
||||
"ty": "PhysicalProperties"
|
||||
},
|
||||
"Rect": {
|
||||
"value": {
|
||||
"Type": "Rect",
|
||||
"Value": [
|
||||
[
|
||||
0.0,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
10.0,
|
||||
15.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"ty": "Rect"
|
||||
},
|
||||
"Vector2int16": {
|
||||
"value": {
|
||||
"Type": "Vector2int16",
|
||||
"Value": [
|
||||
-300,
|
||||
300
|
||||
]
|
||||
},
|
||||
"ty": "Vector2int16"
|
||||
}
|
||||
}
|
||||
43997
plugin/rbx_dom_lua/src/database.json
Normal file
43997
plugin/rbx_dom_lua/src/database.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
local ReflectionDatabase = require(script.ReflectionDatabase)
|
||||
local database = require(script.database)
|
||||
local Error = require(script.Error)
|
||||
local PropertyDescriptor = require(script.PropertyDescriptor)
|
||||
|
||||
|
|
@ -6,29 +6,31 @@ local function findCanonicalPropertyDescriptor(className, propertyName)
|
|||
local currentClassName = className
|
||||
|
||||
repeat
|
||||
local currentClass = ReflectionDatabase.classes[currentClassName]
|
||||
local currentClass = database.Classes[currentClassName]
|
||||
|
||||
if currentClass == nil then
|
||||
return currentClass
|
||||
end
|
||||
|
||||
local propertyData = currentClass.properties[propertyName]
|
||||
local propertyData = currentClass.Properties[propertyName]
|
||||
if propertyData ~= nil then
|
||||
if propertyData.isCanonical then
|
||||
local canonicalData = propertyData.Kind.Canonical
|
||||
if canonicalData ~= nil then
|
||||
return PropertyDescriptor.fromRaw(propertyData, currentClassName, propertyName)
|
||||
end
|
||||
|
||||
if propertyData.canonicalName ~= nil then
|
||||
local aliasData = propertyData.Kind.Alias
|
||||
if aliasData ~= nil then
|
||||
return PropertyDescriptor.fromRaw(
|
||||
currentClass.properties[propertyData.canonicalName],
|
||||
currentClass.properties[aliasData.AliasFor],
|
||||
currentClassName,
|
||||
propertyData.canonicalName)
|
||||
aliasData.AliasFor)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
currentClassName = currentClass.superclass
|
||||
currentClassName = currentClass.Superclass
|
||||
until currentClassName == nil
|
||||
|
||||
return nil
|
||||
|
|
|
|||
4
plugin/rbx_dom_lua/test
Normal file
4
plugin/rbx_dom_lua/test
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
rojo build test-place.project.json -o TestPlace.rbxlx
|
||||
run-in-roblox --script run-tests.lua --place TestPlace.rbxlx
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
"$className": "ServerScriptService",
|
||||
|
||||
"Run Tests": {
|
||||
"$path": "test.server.lua"
|
||||
"$path": "run-tests.lua"
|
||||
}
|
||||
},
|
||||
"Players": {
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local LIB_ROOT = ReplicatedStorage.RbxDom
|
||||
|
||||
local TestEZ = require(ReplicatedStorage.TestEZ)
|
||||
|
||||
TestEZ.TestBootstrap:run({LIB_ROOT})
|
||||
|
|
@ -10,7 +10,7 @@ expression: contents
|
|||
<Item class="IntValue" referent="1">
|
||||
<Properties>
|
||||
<string name="Name">simple-model</string>
|
||||
<int name="Value">5</int>
|
||||
<int64 name="Value">5</int64>
|
||||
</Properties>
|
||||
<Item class="Folder" referent="2">
|
||||
<Properties>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
source: tests/tests/build.rs
|
||||
expression: contents
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="DataModel" referent="0">
|
||||
<Properties>
|
||||
<string name="Name">unresolved-values</string>
|
||||
</Properties>
|
||||
<Item class="Lighting" referent="1">
|
||||
<Properties>
|
||||
<string name="Name">Lighting</string>
|
||||
<Color3 name="Ambient">
|
||||
<R>1</R>
|
||||
<G>0</G>
|
||||
<B>0</B>
|
||||
</Color3>
|
||||
<token name="Technology">1</token>
|
||||
</Properties>
|
||||
</Item>
|
||||
<Item class="Workspace" referent="2">
|
||||
<Properties>
|
||||
<string name="Name">Workspace</string>
|
||||
</Properties>
|
||||
<Item class="BoolValue" referent="3">
|
||||
<Properties>
|
||||
<string name="Name">Bool</string>
|
||||
<bool name="Value">true</bool>
|
||||
</Properties>
|
||||
</Item>
|
||||
<Item class="Part" referent="4">
|
||||
<Properties>
|
||||
<string name="Name">Color</string>
|
||||
<Color3 name="Color3uint8">
|
||||
<R>0.5</R>
|
||||
<G>0.25</G>
|
||||
<B>0</B>
|
||||
</Color3>
|
||||
</Properties>
|
||||
</Item>
|
||||
<Item class="NumberValue" referent="5">
|
||||
<Properties>
|
||||
<string name="Name">Float</string>
|
||||
<double name="Value">123.5</double>
|
||||
</Properties>
|
||||
</Item>
|
||||
<Item class="IntValue" referent="6">
|
||||
<Properties>
|
||||
<string name="Name">Int</string>
|
||||
<int64 name="Value">65</int64>
|
||||
</Properties>
|
||||
</Item>
|
||||
</Item>
|
||||
</Item>
|
||||
</roblox>
|
||||
43
rojo-test/build-tests/unresolved_values/default.project.json
Normal file
43
rojo-test/build-tests/unresolved_values/default.project.json
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "unresolved-values",
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
|
||||
"Lighting": {
|
||||
"$properties": {
|
||||
"Technology": "Voxel",
|
||||
"Ambient": [1, 0, 0]
|
||||
}
|
||||
},
|
||||
|
||||
"Workspace": {
|
||||
"Color": {
|
||||
"$className": "Part",
|
||||
"$properties": {
|
||||
"Color": [0.5, 0.25, 0]
|
||||
}
|
||||
},
|
||||
|
||||
"Bool": {
|
||||
"$className": "BoolValue",
|
||||
"$properties": {
|
||||
"Value": true
|
||||
}
|
||||
},
|
||||
|
||||
"Int": {
|
||||
"$className": "IntValue",
|
||||
"$properties": {
|
||||
"Value": 65
|
||||
}
|
||||
},
|
||||
|
||||
"Float": {
|
||||
"$className": "NumberValue",
|
||||
"$properties": {
|
||||
"Value": 123.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: add_folder
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
id-3:
|
||||
Children: []
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: add_folder
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
messageCursor: 0
|
||||
sessionId: id-1
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: edit_init
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties:
|
||||
Source:
|
||||
Type: String
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: edit_init
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties:
|
||||
Source:
|
||||
Type: String
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: true
|
||||
Name: empty
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
messageCursor: 0
|
||||
sessionId: id-1
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: move_folder_of_stuff
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
id-3:
|
||||
Children:
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: move_folder_of_stuff
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
messageCursor: 0
|
||||
sessionId: id-1
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: remove_file
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
messageCursor: 1
|
||||
sessionId: id-1
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: remove_file
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
id-3:
|
||||
Children: []
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: scripts
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
id-3:
|
||||
Children: []
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ instances:
|
|||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: scripts
|
||||
Parent: ~
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
id-3:
|
||||
Children: []
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
use crossbeam_channel::{select, Receiver, RecvError, Sender};
|
||||
use jod_thread::JoinHandle;
|
||||
use memofs::{IoResultExt, Vfs, VfsEvent};
|
||||
use rbx_dom_weak::{RbxId, RbxValue};
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
|
||||
use crate::{
|
||||
message_queue::MessageQueue,
|
||||
|
|
@ -179,7 +179,7 @@ impl JobThreadContext {
|
|||
InstigatingSource::Path(path) => fs::remove_file(path).unwrap(),
|
||||
InstigatingSource::ProjectNode(_, _, _, _) => {
|
||||
log::warn!(
|
||||
"Cannot remove instance {}, it's from a project file",
|
||||
"Cannot remove instance {:?}, it's from a project file",
|
||||
id
|
||||
);
|
||||
}
|
||||
|
|
@ -187,12 +187,12 @@ impl JobThreadContext {
|
|||
} else {
|
||||
// TODO
|
||||
log::warn!(
|
||||
"Cannot remove instance {}, it is not an instigating source.",
|
||||
"Cannot remove instance {:?}, it is not an instigating source.",
|
||||
id
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::warn!("Cannot remove instance {}, it does not exist.", id);
|
||||
log::warn!("Cannot remove instance {:?}, it does not exist.", id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -219,7 +219,7 @@ impl JobThreadContext {
|
|||
{
|
||||
match instigating_source {
|
||||
InstigatingSource::Path(path) => {
|
||||
if let Some(RbxValue::String { value }) = changed_value {
|
||||
if let Some(Variant::String(value)) = changed_value {
|
||||
fs::write(path, value).unwrap();
|
||||
} else {
|
||||
log::warn!("Cannot change Source to non-string value.");
|
||||
|
|
@ -227,14 +227,14 @@ impl JobThreadContext {
|
|||
}
|
||||
InstigatingSource::ProjectNode(_, _, _, _) => {
|
||||
log::warn!(
|
||||
"Cannot remove instance {}, it's from a project file",
|
||||
"Cannot remove instance {:?}, it's from a project file",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!(
|
||||
"Cannot update instance {}, it is not an instigating source.",
|
||||
"Cannot update instance {:?}, it is not an instigating source.",
|
||||
id
|
||||
);
|
||||
}
|
||||
|
|
@ -243,7 +243,7 @@ impl JobThreadContext {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!("Cannot update instance {}, it does not exist.", id);
|
||||
log::warn!("Cannot update instance {:?}, it does not exist.", id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ impl JobThreadContext {
|
|||
}
|
||||
}
|
||||
|
||||
fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: RbxId) -> Option<AppliedPatchSet> {
|
||||
fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<AppliedPatchSet> {
|
||||
let metadata = tree
|
||||
.get_metadata(id)
|
||||
.expect("metadata missing for instance present in tree");
|
||||
|
|
@ -263,7 +263,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: RbxId) -> Optio
|
|||
Some(path) => path,
|
||||
None => {
|
||||
log::error!(
|
||||
"Instance {} did not have an instigating source, but was considered for an update.",
|
||||
"Instance {:?} did not have an instigating source, but was considered for an update.",
|
||||
id
|
||||
);
|
||||
log::error!("This is a bug. Please file an issue!");
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ fn write_model(tree: &RojoTree, options: &BuildCommand) -> Result<(), anyhow::Er
|
|||
}
|
||||
OutputKind::Rbxlx => {
|
||||
// Place files don't contain an entry for the DataModel, but our
|
||||
// RbxTree representation does.
|
||||
// WeakDom representation does.
|
||||
|
||||
let root_instance = tree.get_instance(root_id).unwrap();
|
||||
let top_level_ids = root_instance.children();
|
||||
|
|
@ -96,17 +96,13 @@ fn write_model(tree: &RojoTree, options: &BuildCommand) -> Result<(), anyhow::Er
|
|||
rbx_xml::to_writer(&mut file, tree.inner(), top_level_ids, xml_encode_config())?;
|
||||
}
|
||||
OutputKind::Rbxm => {
|
||||
rbx_binary::encode(tree.inner(), &[root_id], &mut file)?;
|
||||
rbx_binary::to_writer_default(&mut file, tree.inner(), &[root_id])?;
|
||||
}
|
||||
OutputKind::Rbxl => {
|
||||
log::warn!("Support for building binary places (rbxl) is still experimental.");
|
||||
log::warn!("Using the XML place format (rbxlx) is recommended instead.");
|
||||
log::warn!("For more info, see https://github.com/rojo-rbx/rojo/issues/180");
|
||||
|
||||
let root_instance = tree.get_instance(root_id).unwrap();
|
||||
let top_level_ids = root_instance.children();
|
||||
|
||||
rbx_binary::encode(tree.inner(), top_level_ids, &mut file)?;
|
||||
rbx_binary::to_writer_default(&mut file, tree.inner(), top_level_ids)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ pub fn install_plugin() -> Result<()> {
|
|||
let tree = session.tree();
|
||||
let root_id = tree.get_root_id();
|
||||
|
||||
rbx_binary::encode(tree.inner(), &[root_id], &mut file)?;
|
||||
rbx_binary::to_writer_default(&mut file, tree.inner(), &[root_id])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,12 +29,11 @@ pub fn upload(options: UploadCommand) -> Result<(), anyhow::Error> {
|
|||
|
||||
let tree = session.tree();
|
||||
let inner_tree = tree.inner();
|
||||
let root_id = inner_tree.get_root_id();
|
||||
let root_instance = inner_tree.get_instance(root_id).unwrap();
|
||||
let root = inner_tree.root();
|
||||
|
||||
let encode_ids = match root_instance.class_name.as_str() {
|
||||
"DataModel" => root_instance.get_children_ids().to_vec(),
|
||||
_ => vec![root_id],
|
||||
let encode_ids = match root.class.as_str() {
|
||||
"DataModel" => root.children().to_vec(),
|
||||
_ => vec![root.referent()],
|
||||
};
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ mod message_queue;
|
|||
mod multimap;
|
||||
mod path_serializer;
|
||||
mod project;
|
||||
mod resolution;
|
||||
mod serve_session;
|
||||
mod session_id;
|
||||
mod snapshot;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use rbx_dom_weak::UnresolvedRbxValue;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::glob::Glob;
|
||||
use crate::{glob::Glob, resolution::UnresolvedValue};
|
||||
|
||||
static PROJECT_FILENAME: &str = "default.project.json";
|
||||
|
||||
|
|
@ -183,7 +182,7 @@ pub struct ProjectNode {
|
|||
default,
|
||||
skip_serializing_if = "HashMap::is_empty"
|
||||
)]
|
||||
pub properties: HashMap<String, UnresolvedRbxValue>,
|
||||
pub properties: HashMap<String, UnresolvedValue>,
|
||||
|
||||
/// Defines the behavior when Rojo encounters unknown instances in Roblox
|
||||
/// Studio during live sync. `$ignoreUnknownInstances` should be considered
|
||||
|
|
|
|||
159
src/resolution.rs
Normal file
159
src/resolution.rs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
use anyhow::format_err;
|
||||
use rbx_dom_weak::types::{
|
||||
Color3, Color3uint8, Content, Enum, Variant, VariantType, Vector2, Vector3,
|
||||
};
|
||||
use rbx_reflection::{DataType, PropertyDescriptor};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum UnresolvedValue {
|
||||
FullyQualified(Variant),
|
||||
Ambiguous(AmbiguousValue),
|
||||
}
|
||||
|
||||
impl UnresolvedValue {
|
||||
pub fn resolve(self, class_name: &str, prop_name: &str) -> anyhow::Result<Variant> {
|
||||
match self {
|
||||
UnresolvedValue::FullyQualified(full) => Ok(full),
|
||||
UnresolvedValue::Ambiguous(partial) => partial.resolve(class_name, prop_name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum AmbiguousValue {
|
||||
Bool(bool),
|
||||
String(String),
|
||||
Number(f64),
|
||||
Array2([f64; 2]),
|
||||
Array3([f64; 3]),
|
||||
Array4([f64; 4]),
|
||||
Array16([f64; 16]),
|
||||
}
|
||||
|
||||
impl AmbiguousValue {
|
||||
pub fn resolve(self, class_name: &str, prop_name: &str) -> anyhow::Result<Variant> {
|
||||
let property = find_descriptor(class_name, prop_name)
|
||||
.ok_or_else(|| format_err!("Unknown property {}.{}", class_name, prop_name))?;
|
||||
|
||||
match &property.data_type {
|
||||
DataType::Enum(enum_name) => {
|
||||
let database = rbx_reflection_database::get();
|
||||
|
||||
let enum_descriptor = database.enums.get(enum_name).ok_or_else(|| {
|
||||
format_err!("Unknown enum {}. This is a Rojo bug!", enum_name)
|
||||
})?;
|
||||
|
||||
let error = |what: &str| {
|
||||
let sample_values = enum_descriptor
|
||||
.items
|
||||
.keys()
|
||||
.take(3)
|
||||
.map(|name| format!(r#""{}""#, name))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
format_err!(
|
||||
"Invalid value for property {}.{}. Got {} but \
|
||||
expected a member of the {} enum such as {}",
|
||||
class_name,
|
||||
prop_name,
|
||||
what,
|
||||
enum_name,
|
||||
sample_values
|
||||
)
|
||||
};
|
||||
|
||||
let value = match self {
|
||||
AmbiguousValue::String(value) => value,
|
||||
unresolved => return Err(error(unresolved.describe())),
|
||||
};
|
||||
|
||||
let resolved = enum_descriptor
|
||||
.items
|
||||
.get(value.as_str())
|
||||
.ok_or_else(|| error(value.as_str()))?;
|
||||
|
||||
Ok(Enum::from_u32(*resolved).into())
|
||||
}
|
||||
DataType::Value(variant_ty) => match (variant_ty, self) {
|
||||
(VariantType::Bool, AmbiguousValue::Bool(value)) => Ok(value.into()),
|
||||
|
||||
(VariantType::Float32, AmbiguousValue::Number(value)) => Ok((value as f32).into()),
|
||||
(VariantType::Float64, AmbiguousValue::Number(value)) => Ok(value.into()),
|
||||
(VariantType::Int32, AmbiguousValue::Number(value)) => Ok((value as i32).into()),
|
||||
(VariantType::Int64, AmbiguousValue::Number(value)) => Ok((value as i64).into()),
|
||||
|
||||
(VariantType::String, AmbiguousValue::String(value)) => Ok(value.into()),
|
||||
(VariantType::Content, AmbiguousValue::String(value)) => {
|
||||
Ok(Content::from(value).into())
|
||||
}
|
||||
|
||||
(VariantType::Vector2, AmbiguousValue::Array2(value)) => {
|
||||
Ok(Vector2::new(value[0] as f32, value[1] as f32).into())
|
||||
}
|
||||
|
||||
(VariantType::Vector3, AmbiguousValue::Array3(value)) => {
|
||||
Ok(Vector3::new(value[0] as f32, value[1] as f32, value[2] as f32).into())
|
||||
}
|
||||
|
||||
(VariantType::Color3, AmbiguousValue::Array3(value)) => {
|
||||
Ok(Color3::new(value[0] as f32, value[1] as f32, value[2] as f32).into())
|
||||
}
|
||||
(VariantType::Color3uint8, AmbiguousValue::Array3(value)) => {
|
||||
let value = Color3uint8::new(
|
||||
(value[0] / 255.0) as u8,
|
||||
(value[1] / 255.0) as u8,
|
||||
(value[2] / 255.0) as u8,
|
||||
);
|
||||
|
||||
Ok(value.into())
|
||||
}
|
||||
|
||||
(_, unresolved) => Err(format_err!(
|
||||
"Wrong type of value for property {}.{}. Expected {:?}, got {}",
|
||||
class_name,
|
||||
prop_name,
|
||||
variant_ty,
|
||||
unresolved.describe(),
|
||||
)),
|
||||
},
|
||||
_ => Err(format_err!(
|
||||
"Unknown data type for property {}.{}",
|
||||
class_name,
|
||||
prop_name
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self) -> &'static str {
|
||||
match self {
|
||||
AmbiguousValue::Bool(_) => "a bool",
|
||||
AmbiguousValue::String(_) => "a string",
|
||||
AmbiguousValue::Number(_) => "a number",
|
||||
AmbiguousValue::Array2(_) => "an array of two numbers",
|
||||
AmbiguousValue::Array3(_) => "an array of three numbers",
|
||||
AmbiguousValue::Array4(_) => "an array of four numbers",
|
||||
AmbiguousValue::Array16(_) => "an array of 16 numbers",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_descriptor(
|
||||
class_name: &str,
|
||||
prop_name: &str,
|
||||
) -> Option<&'static PropertyDescriptor<'static>> {
|
||||
let database = rbx_reflection_database::get();
|
||||
let mut current_class_name = class_name;
|
||||
|
||||
loop {
|
||||
let class = database.classes.get(current_class_name)?;
|
||||
if let Some(descriptor) = class.properties.get(prop_name) {
|
||||
return Some(descriptor);
|
||||
}
|
||||
|
||||
current_class_name = class.superclass.as_deref()?;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ use std::{
|
|||
use crossbeam_channel::Sender;
|
||||
use memofs::IoResultExt;
|
||||
use memofs::Vfs;
|
||||
use rbx_dom_weak::RbxInstanceProperties;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -19,8 +18,8 @@ use crate::{
|
|||
project::{Project, ProjectError},
|
||||
session_id::SessionId,
|
||||
snapshot::{
|
||||
apply_patch_set, compute_patch_set, AppliedPatchSet, InstanceContext,
|
||||
InstancePropertiesWithMeta, PatchSet, RojoTree,
|
||||
apply_patch_set, compute_patch_set, AppliedPatchSet, InstanceContext, InstanceSnapshot,
|
||||
PatchSet, RojoTree,
|
||||
},
|
||||
snapshot_middleware::snapshot_from_vfs,
|
||||
};
|
||||
|
|
@ -118,14 +117,7 @@ impl ServeSession {
|
|||
}
|
||||
};
|
||||
|
||||
let mut tree = RojoTree::new(InstancePropertiesWithMeta {
|
||||
properties: RbxInstanceProperties {
|
||||
name: "ROOT".to_owned(),
|
||||
class_name: "Folder".to_owned(),
|
||||
properties: Default::default(),
|
||||
},
|
||||
metadata: Default::default(),
|
||||
});
|
||||
let mut tree = RojoTree::new(InstanceSnapshot::new());
|
||||
|
||||
let root_id = tree.get_root_id();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
use rbx_dom_weak::{RbxId, RbxTree, RbxValue};
|
||||
use rbx_dom_weak::{
|
||||
types::{Ref, Variant},
|
||||
WeakDom,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::InstanceMetadata;
|
||||
|
|
@ -11,11 +14,12 @@ use super::InstanceMetadata;
|
|||
///
|
||||
// Possible future improvements:
|
||||
// - Use refcounted/interned strings
|
||||
// - Replace use of RbxValue with a sum of RbxValue + borrowed value
|
||||
// - Replace use of Variant with a sum of Variant + borrowed value
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct InstanceSnapshot {
|
||||
// FIXME: Don't use Option<Ref> anymore!
|
||||
/// A temporary ID applied to the snapshot that's used for Ref properties.
|
||||
pub snapshot_id: Option<RbxId>,
|
||||
pub snapshot_id: Option<Ref>,
|
||||
|
||||
/// Rojo-specific metadata associated with the instance.
|
||||
pub metadata: InstanceMetadata,
|
||||
|
|
@ -27,7 +31,7 @@ pub struct InstanceSnapshot {
|
|||
pub class_name: Cow<'static, str>,
|
||||
|
||||
/// All other properties of the instance, weakly-typed.
|
||||
pub properties: HashMap<String, RbxValue>,
|
||||
pub properties: HashMap<String, Variant>,
|
||||
|
||||
/// The children of the instance represented as more snapshots.
|
||||
///
|
||||
|
|
@ -61,7 +65,16 @@ impl InstanceSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn properties(self, properties: impl Into<HashMap<String, RbxValue>>) -> Self {
|
||||
pub fn property<K, V>(mut self, key: K, value: V) -> Self
|
||||
where
|
||||
K: Into<String>,
|
||||
V: Into<Variant>,
|
||||
{
|
||||
self.properties.insert(key.into(), value.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn properties(self, properties: impl Into<HashMap<String, Variant>>) -> Self {
|
||||
Self {
|
||||
properties: properties.into(),
|
||||
..self
|
||||
|
|
@ -75,6 +88,13 @@ impl InstanceSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn snapshot_id(self, snapshot_id: Option<Ref>) -> Self {
|
||||
Self {
|
||||
snapshot_id,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metadata(self, metadata: impl Into<InstanceMetadata>) -> Self {
|
||||
Self {
|
||||
metadata: metadata.into(),
|
||||
|
|
@ -82,15 +102,13 @@ impl InstanceSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_tree(tree: &RbxTree, id: RbxId) -> Self {
|
||||
let instance = tree
|
||||
.get_instance(id)
|
||||
.expect("instance did not exist in tree");
|
||||
pub fn from_tree(tree: &WeakDom, id: Ref) -> Self {
|
||||
let instance = tree.get_by_ref(id).expect("instance did not exist in tree");
|
||||
|
||||
let children = instance
|
||||
.get_children_ids()
|
||||
.children()
|
||||
.iter()
|
||||
.cloned()
|
||||
.copied()
|
||||
.map(|id| Self::from_tree(tree, id))
|
||||
.collect();
|
||||
|
||||
|
|
@ -98,7 +116,7 @@ impl InstanceSnapshot {
|
|||
snapshot_id: Some(id),
|
||||
metadata: InstanceMetadata::default(),
|
||||
name: Cow::Owned(instance.name.clone()),
|
||||
class_name: Cow::Owned(instance.class_name.clone()),
|
||||
class_name: Cow::Owned(instance.class.clone()),
|
||||
properties: instance.properties.clone(),
|
||||
children,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,19 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rbx_dom_weak::{RbxId, RbxValue};
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{InstanceMetadata, InstanceSnapshot};
|
||||
|
||||
/// A set of different kinds of patches that can be applied to an RbxTree.
|
||||
/// A set of different kinds of patches that can be applied to an WeakDom.
|
||||
///
|
||||
/// These patches shouldn't be persisted: there's no mechanism in place to make
|
||||
/// sure that another patch wasn't applied before this one that could cause a
|
||||
/// conflict!
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PatchSet {
|
||||
pub removed_instances: Vec<RbxId>,
|
||||
pub removed_instances: Vec<Ref>,
|
||||
pub added_instances: Vec<PatchAdd>,
|
||||
pub updated_instances: Vec<PatchUpdate>,
|
||||
}
|
||||
|
|
@ -32,20 +32,20 @@ impl<'a> PatchSet {
|
|||
/// A patch containing an instance that was added to the tree.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PatchAdd {
|
||||
pub parent_id: RbxId,
|
||||
pub parent_id: Ref,
|
||||
pub instance: InstanceSnapshot,
|
||||
}
|
||||
|
||||
/// A patch indicating that properties of an instance changed.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PatchUpdate {
|
||||
pub id: RbxId,
|
||||
pub id: Ref,
|
||||
pub changed_name: Option<String>,
|
||||
pub changed_class_name: Option<String>,
|
||||
|
||||
/// Contains all changed properties. If a property is assigned to `None`,
|
||||
/// then that property has been removed.
|
||||
pub changed_properties: HashMap<String, Option<RbxValue>>,
|
||||
pub changed_properties: HashMap<String, Option<Variant>>,
|
||||
|
||||
/// Changed Rojo-specific metadata, if any of it changed.
|
||||
pub changed_metadata: Option<InstanceMetadata>,
|
||||
|
|
@ -63,8 +63,8 @@ pub struct PatchUpdate {
|
|||
// current values in all fields.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct AppliedPatchSet {
|
||||
pub removed: Vec<RbxId>,
|
||||
pub added: Vec<RbxId>,
|
||||
pub removed: Vec<Ref>,
|
||||
pub added: Vec<Ref>,
|
||||
pub updated: Vec<AppliedPatchUpdate>,
|
||||
}
|
||||
|
||||
|
|
@ -80,17 +80,17 @@ impl AppliedPatchSet {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AppliedPatchUpdate {
|
||||
pub id: RbxId,
|
||||
pub id: Ref,
|
||||
|
||||
// TODO: Store previous values in order to detect application conflicts
|
||||
pub changed_name: Option<String>,
|
||||
pub changed_class_name: Option<String>,
|
||||
pub changed_properties: HashMap<String, Option<RbxValue>>,
|
||||
pub changed_properties: HashMap<String, Option<Variant>>,
|
||||
pub changed_metadata: Option<InstanceMetadata>,
|
||||
}
|
||||
|
||||
impl AppliedPatchUpdate {
|
||||
pub fn new(id: RbxId) -> Self {
|
||||
pub fn new(id: Ref) -> Self {
|
||||
Self {
|
||||
id,
|
||||
changed_name: None,
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rbx_dom_weak::{RbxId, RbxInstanceProperties, RbxValue};
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
|
||||
use super::{
|
||||
patch::{AppliedPatchSet, AppliedPatchUpdate, PatchSet, PatchUpdate},
|
||||
InstancePropertiesWithMeta, InstanceSnapshot, RojoTree,
|
||||
InstanceSnapshot, RojoTree,
|
||||
};
|
||||
|
||||
/// Consumes the input `PatchSet`, applying all of its prescribed changes to the
|
||||
|
|
@ -37,7 +37,7 @@ pub fn apply_patch_set(tree: &mut RojoTree, patch_set: PatchSet) -> AppliedPatch
|
|||
struct PatchApplyContext {
|
||||
/// A map from transient snapshot IDs (generated by snapshot middleware) to
|
||||
/// instance IDs in the actual tree. These are both the same data type so
|
||||
/// that they fit into the same `RbxValue::Ref` type.
|
||||
/// that they fit into the same `Variant::Ref` type.
|
||||
///
|
||||
/// At this point in the patch process, IDs in instance properties have been
|
||||
/// partially translated from 'snapshot space' into 'tree space' by the
|
||||
|
|
@ -53,7 +53,7 @@ struct PatchApplyContext {
|
|||
/// #2 should not occur in well-formed projects, but is indistinguishable
|
||||
/// from #1 right now. It could happen if two model files try to reference
|
||||
/// eachother.
|
||||
snapshot_id_to_instance_id: HashMap<RbxId, RbxId>,
|
||||
snapshot_id_to_instance_id: HashMap<Ref, Ref>,
|
||||
|
||||
/// The properties of instances added by the current `PatchSet`.
|
||||
///
|
||||
|
|
@ -68,7 +68,7 @@ struct PatchApplyContext {
|
|||
///
|
||||
/// This doesn't affect updated instances, since they're always applied
|
||||
/// after we've added all the instances from the patch.
|
||||
added_instance_properties: HashMap<RbxId, HashMap<String, RbxValue>>,
|
||||
added_instance_properties: HashMap<Ref, HashMap<String, Variant>>,
|
||||
|
||||
/// The current applied patch result, describing changes made to the tree.
|
||||
applied_patch_set: AppliedPatchSet,
|
||||
|
|
@ -93,11 +93,10 @@ fn finalize_patch_application(context: PatchApplyContext, tree: &mut RojoTree) -
|
|||
.expect("Invalid instance ID in deferred property map");
|
||||
|
||||
for (key, mut property_value) in properties {
|
||||
if let RbxValue::Ref { value: Some(id) } = property_value {
|
||||
if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(&id) {
|
||||
property_value = RbxValue::Ref {
|
||||
value: Some(instance_id),
|
||||
};
|
||||
if let Variant::Ref(referent) = property_value {
|
||||
if let Some(&instance_referent) = context.snapshot_id_to_instance_id.get(&referent)
|
||||
{
|
||||
property_value = Variant::Ref(instance_referent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,50 +107,40 @@ fn finalize_patch_application(context: PatchApplyContext, tree: &mut RojoTree) -
|
|||
context.applied_patch_set
|
||||
}
|
||||
|
||||
fn apply_remove_instance(context: &mut PatchApplyContext, tree: &mut RojoTree, removed_id: RbxId) {
|
||||
match tree.remove_instance(removed_id) {
|
||||
Some(_) => context.applied_patch_set.removed.push(removed_id),
|
||||
None => {
|
||||
log::warn!(
|
||||
"Patch misapplication: Tried to remove instance {} but it did not exist.",
|
||||
removed_id
|
||||
);
|
||||
}
|
||||
}
|
||||
fn apply_remove_instance(context: &mut PatchApplyContext, tree: &mut RojoTree, removed_id: Ref) {
|
||||
tree.remove(removed_id);
|
||||
context.applied_patch_set.removed.push(removed_id);
|
||||
}
|
||||
|
||||
fn apply_add_child(
|
||||
context: &mut PatchApplyContext,
|
||||
tree: &mut RojoTree,
|
||||
parent_id: RbxId,
|
||||
parent_id: Ref,
|
||||
snapshot: InstanceSnapshot,
|
||||
) {
|
||||
let properties = InstancePropertiesWithMeta {
|
||||
properties: RbxInstanceProperties {
|
||||
name: snapshot.name.into_owned(),
|
||||
class_name: snapshot.class_name.into_owned(),
|
||||
let snapshot_id = snapshot.snapshot_id;
|
||||
let properties = snapshot.properties;
|
||||
let children = snapshot.children;
|
||||
|
||||
// Property assignment is deferred until after we know about all
|
||||
// instances in this patch. See `PatchApplyContext` for details.
|
||||
properties: HashMap::new(),
|
||||
},
|
||||
metadata: snapshot.metadata,
|
||||
};
|
||||
|
||||
let id = tree.insert_instance(properties, parent_id);
|
||||
// Property application is deferred until after all children
|
||||
// are constructed. This helps apply referents correctly.
|
||||
let remaining_snapshot = InstanceSnapshot::new()
|
||||
.name(snapshot.name)
|
||||
.class_name(snapshot.class_name)
|
||||
.metadata(snapshot.metadata)
|
||||
.snapshot_id(snapshot.snapshot_id);
|
||||
|
||||
let id = tree.insert_instance(parent_id, remaining_snapshot);
|
||||
context.applied_patch_set.added.push(id);
|
||||
|
||||
context
|
||||
.added_instance_properties
|
||||
.insert(id, snapshot.properties);
|
||||
context.added_instance_properties.insert(id, properties);
|
||||
|
||||
if let Some(snapshot_id) = snapshot.snapshot_id {
|
||||
if let Some(snapshot_id) = snapshot_id {
|
||||
context.snapshot_id_to_instance_id.insert(snapshot_id, id);
|
||||
}
|
||||
|
||||
for child_snapshot in snapshot.children {
|
||||
apply_add_child(context, tree, id, child_snapshot);
|
||||
for child in children {
|
||||
apply_add_child(context, tree, id, child);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +156,7 @@ fn apply_update_child(context: &mut PatchApplyContext, tree: &mut RojoTree, patc
|
|||
Some(instance) => instance,
|
||||
None => {
|
||||
log::warn!(
|
||||
"Patch misapplication: Instance {}, referred to by update patch, did not exist.",
|
||||
"Patch misapplication: Instance {:?}, referred to by update patch, did not exist.",
|
||||
patch.id
|
||||
);
|
||||
return;
|
||||
|
|
@ -189,23 +178,24 @@ fn apply_update_child(context: &mut PatchApplyContext, tree: &mut RojoTree, patc
|
|||
// Ref values need to be potentially rewritten from snapshot IDs to
|
||||
// instance IDs if they referred to an instance that was created as
|
||||
// part of this patch.
|
||||
Some(RbxValue::Ref { value: Some(id) }) => {
|
||||
Some(Variant::Ref(referent)) => {
|
||||
if referent.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If our ID is not found in this map, then it either refers to
|
||||
// an existing instance NOT added by this patch, or there was an
|
||||
// error. See `PatchApplyContext::snapshot_id_to_instance_id`
|
||||
// for more info.
|
||||
let new_id = context
|
||||
let new_referent = context
|
||||
.snapshot_id_to_instance_id
|
||||
.get(&id)
|
||||
.get(&referent)
|
||||
.copied()
|
||||
.unwrap_or(id);
|
||||
.unwrap_or(referent);
|
||||
|
||||
instance.properties_mut().insert(
|
||||
key.clone(),
|
||||
RbxValue::Ref {
|
||||
value: Some(new_id),
|
||||
},
|
||||
);
|
||||
instance
|
||||
.properties_mut()
|
||||
.insert(key.clone(), Variant::Ref(new_referent));
|
||||
}
|
||||
Some(ref value) => {
|
||||
instance.properties_mut().insert(key.clone(), value.clone());
|
||||
|
|
@ -225,10 +215,10 @@ fn apply_update_child(context: &mut PatchApplyContext, tree: &mut RojoTree, patc
|
|||
mod test {
|
||||
use super::*;
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use maplit::hashmap;
|
||||
use rbx_dom_weak::RbxValue;
|
||||
use rbx_dom_weak::types::Variant;
|
||||
|
||||
use super::super::PatchAdd;
|
||||
|
||||
|
|
@ -236,14 +226,7 @@ mod test {
|
|||
fn add_from_empty() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let mut tree = RojoTree::new(InstancePropertiesWithMeta {
|
||||
properties: RbxInstanceProperties {
|
||||
name: "Folder".to_owned(),
|
||||
class_name: "Folder".to_owned(),
|
||||
properties: HashMap::new(),
|
||||
},
|
||||
metadata: Default::default(),
|
||||
});
|
||||
let mut tree = RojoTree::new(InstanceSnapshot::new());
|
||||
|
||||
let root_id = tree.get_root_id();
|
||||
|
||||
|
|
@ -253,7 +236,7 @@ mod test {
|
|||
name: Cow::Borrowed("Foo"),
|
||||
class_name: Cow::Borrowed("Bar"),
|
||||
properties: hashmap! {
|
||||
"Baz".to_owned() => RbxValue::Int32 { value: 5 },
|
||||
"Baz".to_owned() => Variant::Int32(5),
|
||||
},
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
|
@ -282,18 +265,14 @@ mod test {
|
|||
fn update_existing() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let mut tree = RojoTree::new(InstancePropertiesWithMeta {
|
||||
properties: RbxInstanceProperties {
|
||||
name: "OldName".to_owned(),
|
||||
class_name: "OldClassName".to_owned(),
|
||||
properties: hashmap! {
|
||||
"Foo".to_owned() => RbxValue::Int32 { value: 7 },
|
||||
"Bar".to_owned() => RbxValue::Int32 { value: 3 },
|
||||
"Unchanged".to_owned() => RbxValue::Int32 { value: -5 },
|
||||
},
|
||||
},
|
||||
metadata: Default::default(),
|
||||
});
|
||||
let mut tree = RojoTree::new(
|
||||
InstanceSnapshot::new()
|
||||
.class_name("OldClassName")
|
||||
.name("OldName")
|
||||
.property("Foo", 7i32)
|
||||
.property("Bar", 3i32)
|
||||
.property("Unchanged", -5i32),
|
||||
);
|
||||
|
||||
let root_id = tree.get_root_id();
|
||||
|
||||
|
|
@ -303,13 +282,13 @@ mod test {
|
|||
changed_class_name: Some("NewClassName".to_owned()),
|
||||
changed_properties: hashmap! {
|
||||
// The value of Foo has changed
|
||||
"Foo".to_owned() => Some(RbxValue::Int32 { value: 8 }),
|
||||
"Foo".to_owned() => Some(Variant::Int32(8)),
|
||||
|
||||
// Bar has been deleted
|
||||
"Bar".to_owned() => None,
|
||||
|
||||
// Baz has been added
|
||||
"Baz".to_owned() => Some(RbxValue::Int32 { value: 10 }),
|
||||
"Baz".to_owned() => Some(Variant::Int32(10)),
|
||||
},
|
||||
changed_metadata: None,
|
||||
};
|
||||
|
|
@ -322,9 +301,9 @@ mod test {
|
|||
apply_patch_set(&mut tree, patch_set);
|
||||
|
||||
let expected_properties = hashmap! {
|
||||
"Foo".to_owned() => RbxValue::Int32 { value: 8 },
|
||||
"Baz".to_owned() => RbxValue::Int32 { value: 10 },
|
||||
"Unchanged".to_owned() => RbxValue::Int32 { value: -5 },
|
||||
"Foo".to_owned() => Variant::Int32(8),
|
||||
"Baz".to_owned() => Variant::Int32(10),
|
||||
"Unchanged".to_owned() => Variant::Int32(-5),
|
||||
};
|
||||
|
||||
let root_instance = tree.get_instance(root_id).unwrap();
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use rbx_dom_weak::{RbxId, RbxValue};
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
|
||||
use super::{
|
||||
patch::{PatchAdd, PatchSet, PatchUpdate},
|
||||
InstanceSnapshot, InstanceWithMeta, RojoTree,
|
||||
};
|
||||
|
||||
pub fn compute_patch_set(snapshot: &InstanceSnapshot, tree: &RojoTree, id: RbxId) -> PatchSet {
|
||||
pub fn compute_patch_set(snapshot: &InstanceSnapshot, tree: &RojoTree, id: Ref) -> PatchSet {
|
||||
let mut patch_set = PatchSet::new();
|
||||
let mut context = ComputePatchContext::default();
|
||||
|
||||
|
|
@ -26,17 +26,15 @@ pub fn compute_patch_set(snapshot: &InstanceSnapshot, tree: &RojoTree, id: RbxId
|
|||
|
||||
#[derive(Default)]
|
||||
struct ComputePatchContext {
|
||||
snapshot_id_to_instance_id: HashMap<RbxId, RbxId>,
|
||||
snapshot_id_to_instance_id: HashMap<Ref, Ref>,
|
||||
}
|
||||
|
||||
fn rewrite_refs_in_updates(context: &ComputePatchContext, updates: &mut [PatchUpdate]) {
|
||||
for update in updates {
|
||||
for property_value in update.changed_properties.values_mut() {
|
||||
if let Some(RbxValue::Ref { value: Some(id) }) = property_value {
|
||||
if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) {
|
||||
*property_value = Some(RbxValue::Ref {
|
||||
value: Some(instance_id),
|
||||
});
|
||||
if let Some(Variant::Ref(referent)) = property_value {
|
||||
if let Some(&instance_ref) = context.snapshot_id_to_instance_id.get(referent) {
|
||||
*property_value = Some(Variant::Ref(instance_ref));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,11 +49,9 @@ fn rewrite_refs_in_additions(context: &ComputePatchContext, additions: &mut [Pat
|
|||
|
||||
fn rewrite_refs_in_snapshot(context: &ComputePatchContext, snapshot: &mut InstanceSnapshot) {
|
||||
for property_value in snapshot.properties.values_mut() {
|
||||
if let RbxValue::Ref { value: Some(id) } = property_value {
|
||||
if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) {
|
||||
*property_value = RbxValue::Ref {
|
||||
value: Some(instance_id),
|
||||
};
|
||||
if let Variant::Ref(referent) = property_value {
|
||||
if let Some(&instance_referent) = context.snapshot_id_to_instance_id.get(referent) {
|
||||
*property_value = Variant::Ref(instance_referent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +65,7 @@ fn compute_patch_set_internal(
|
|||
context: &mut ComputePatchContext,
|
||||
snapshot: &InstanceSnapshot,
|
||||
tree: &RojoTree,
|
||||
id: RbxId,
|
||||
id: Ref,
|
||||
patch_set: &mut PatchSet,
|
||||
) {
|
||||
if let Some(snapshot_id) = snapshot.snapshot_id {
|
||||
|
|
@ -154,7 +150,7 @@ fn compute_children_patches(
|
|||
context: &mut ComputePatchContext,
|
||||
snapshot: &InstanceSnapshot,
|
||||
tree: &RojoTree,
|
||||
id: RbxId,
|
||||
id: Ref,
|
||||
patch_set: &mut PatchSet,
|
||||
) {
|
||||
let instance = tree
|
||||
|
|
@ -224,9 +220,6 @@ mod test {
|
|||
use std::borrow::Cow;
|
||||
|
||||
use maplit::hashmap;
|
||||
use rbx_dom_weak::RbxInstanceProperties;
|
||||
|
||||
use super::super::InstancePropertiesWithMeta;
|
||||
|
||||
/// This test makes sure that rewriting refs in instance update patches to
|
||||
/// instances that already exists works. We should be able to correlate the
|
||||
|
|
@ -234,26 +227,17 @@ mod test {
|
|||
/// value before returning from compute_patch_set.
|
||||
#[test]
|
||||
fn rewrite_ref_existing_instance_update() {
|
||||
let tree = RojoTree::new(InstancePropertiesWithMeta {
|
||||
properties: RbxInstanceProperties {
|
||||
name: "foo".to_owned(),
|
||||
class_name: "foo".to_owned(),
|
||||
properties: HashMap::new(),
|
||||
},
|
||||
metadata: Default::default(),
|
||||
});
|
||||
let tree = RojoTree::new(InstanceSnapshot::new().name("foo").class_name("foo"));
|
||||
|
||||
let root_id = tree.get_root_id();
|
||||
|
||||
// This snapshot should be identical to the existing tree except for the
|
||||
// addition of a prop named Self, which is a self-referential Ref.
|
||||
let snapshot_id = RbxId::new();
|
||||
let snapshot_id = Ref::new();
|
||||
let snapshot = InstanceSnapshot {
|
||||
snapshot_id: Some(snapshot_id),
|
||||
properties: hashmap! {
|
||||
"Self".to_owned() => RbxValue::Ref {
|
||||
value: Some(snapshot_id),
|
||||
}
|
||||
"Self".to_owned() => Variant::Ref(snapshot_id),
|
||||
},
|
||||
|
||||
metadata: Default::default(),
|
||||
|
|
@ -270,9 +254,7 @@ mod test {
|
|||
changed_name: None,
|
||||
changed_class_name: None,
|
||||
changed_properties: hashmap! {
|
||||
"Self".to_owned() => Some(RbxValue::Ref {
|
||||
value: Some(root_id),
|
||||
}),
|
||||
"Self".to_owned() => Some(Variant::Ref(root_id)),
|
||||
},
|
||||
changed_metadata: None,
|
||||
}],
|
||||
|
|
@ -288,26 +270,17 @@ mod test {
|
|||
/// one.
|
||||
#[test]
|
||||
fn rewrite_ref_existing_instance_addition() {
|
||||
let tree = RojoTree::new(InstancePropertiesWithMeta {
|
||||
properties: RbxInstanceProperties {
|
||||
name: "foo".to_owned(),
|
||||
class_name: "foo".to_owned(),
|
||||
properties: HashMap::new(),
|
||||
},
|
||||
metadata: Default::default(),
|
||||
});
|
||||
let tree = RojoTree::new(InstanceSnapshot::new().name("foo").class_name("foo"));
|
||||
|
||||
let root_id = tree.get_root_id();
|
||||
|
||||
// This patch describes the existing instance with a new child added.
|
||||
let snapshot_id = RbxId::new();
|
||||
let snapshot_id = Ref::new();
|
||||
let snapshot = InstanceSnapshot {
|
||||
snapshot_id: Some(snapshot_id),
|
||||
children: vec![InstanceSnapshot {
|
||||
properties: hashmap! {
|
||||
"Self".to_owned() => RbxValue::Ref {
|
||||
value: Some(snapshot_id),
|
||||
},
|
||||
"Self".to_owned() => Variant::Ref(snapshot_id),
|
||||
},
|
||||
|
||||
snapshot_id: None,
|
||||
|
|
@ -332,9 +305,7 @@ mod test {
|
|||
snapshot_id: None,
|
||||
metadata: Default::default(),
|
||||
properties: hashmap! {
|
||||
"Self".to_owned() => RbxValue::Ref {
|
||||
value: Some(root_id),
|
||||
},
|
||||
"Self".to_owned() => Variant::Ref(root_id),
|
||||
},
|
||||
name: Cow::Borrowed("child"),
|
||||
class_name: Cow::Borrowed("child"),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
use insta::assert_yaml_snapshot;
|
||||
use maplit::hashmap;
|
||||
use rbx_dom_weak::{RbxInstanceProperties, RbxValue};
|
||||
|
||||
use rojo_insta_ext::RedactionMap;
|
||||
|
||||
use crate::{
|
||||
snapshot::{apply_patch_set, InstancePropertiesWithMeta, PatchSet, PatchUpdate, RojoTree},
|
||||
snapshot::{apply_patch_set, InstanceSnapshot, PatchSet, PatchUpdate, RojoTree},
|
||||
tree_view::{intern_tree, view_tree},
|
||||
};
|
||||
|
||||
|
|
@ -49,9 +48,7 @@ fn add_property() {
|
|||
changed_name: None,
|
||||
changed_class_name: None,
|
||||
changed_properties: hashmap! {
|
||||
"Foo".to_owned() => Some(RbxValue::String {
|
||||
value: "Value of Foo".to_owned(),
|
||||
}),
|
||||
"Foo".to_owned() => Some("Value of Foo".into()),
|
||||
},
|
||||
changed_metadata: None,
|
||||
}],
|
||||
|
|
@ -78,12 +75,9 @@ fn remove_property() {
|
|||
let root_id = tree.get_root_id();
|
||||
let mut root_instance = tree.get_instance_mut(root_id).unwrap();
|
||||
|
||||
root_instance.properties_mut().insert(
|
||||
"Foo".to_owned(),
|
||||
RbxValue::String {
|
||||
value: "Should be removed".to_owned(),
|
||||
},
|
||||
);
|
||||
root_instance
|
||||
.properties_mut()
|
||||
.insert("Foo".to_owned(), "Should be removed".into());
|
||||
}
|
||||
|
||||
let tree_view = view_tree(&tree, &mut redactions);
|
||||
|
|
@ -112,12 +106,5 @@ fn remove_property() {
|
|||
}
|
||||
|
||||
fn empty_tree() -> RojoTree {
|
||||
RojoTree::new(InstancePropertiesWithMeta {
|
||||
properties: RbxInstanceProperties {
|
||||
name: "ROOT".to_owned(),
|
||||
class_name: "ROOT".to_owned(),
|
||||
properties: Default::default(),
|
||||
},
|
||||
metadata: Default::default(),
|
||||
})
|
||||
RojoTree::new(InstanceSnapshot::new().name("ROOT").class_name("ROOT"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ use std::borrow::Cow;
|
|||
|
||||
use insta::assert_yaml_snapshot;
|
||||
use maplit::hashmap;
|
||||
use rbx_dom_weak::{RbxInstanceProperties, RbxValue};
|
||||
|
||||
use rojo_insta_ext::RedactionMap;
|
||||
|
||||
use crate::snapshot::{compute_patch_set, InstancePropertiesWithMeta, InstanceSnapshot, RojoTree};
|
||||
use crate::snapshot::{compute_patch_set, InstanceSnapshot, RojoTree};
|
||||
|
||||
#[test]
|
||||
fn set_name_and_class_name() {
|
||||
|
|
@ -43,9 +42,7 @@ fn set_property() {
|
|||
name: Cow::Borrowed("ROOT"),
|
||||
class_name: Cow::Borrowed("ROOT"),
|
||||
properties: hashmap! {
|
||||
"PropertyName".to_owned() => RbxValue::String {
|
||||
value: "Hello, world!".to_owned(),
|
||||
},
|
||||
"PropertyName".to_owned() => "Hello, world!".into(),
|
||||
},
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
|
@ -68,9 +65,7 @@ fn remove_property() {
|
|||
let mut root_instance = tree.get_instance_mut(root_id).unwrap();
|
||||
root_instance.properties_mut().insert(
|
||||
"Foo".to_owned(),
|
||||
RbxValue::String {
|
||||
value: "This should be removed by the patch.".to_owned(),
|
||||
},
|
||||
"This should be removed by the patch.".into(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -128,15 +123,8 @@ fn remove_child() {
|
|||
{
|
||||
let root_id = tree.get_root_id();
|
||||
let new_id = tree.insert_instance(
|
||||
InstancePropertiesWithMeta {
|
||||
properties: RbxInstanceProperties {
|
||||
name: "Should not appear in snapshot".to_owned(),
|
||||
class_name: "Folder".to_owned(),
|
||||
properties: Default::default(),
|
||||
},
|
||||
metadata: Default::default(),
|
||||
},
|
||||
root_id,
|
||||
InstanceSnapshot::new().name("Should not appear in snapshot"),
|
||||
);
|
||||
|
||||
redactions.intern(new_id);
|
||||
|
|
@ -158,12 +146,5 @@ fn remove_child() {
|
|||
}
|
||||
|
||||
fn empty_tree() -> RojoTree {
|
||||
RojoTree::new(InstancePropertiesWithMeta {
|
||||
properties: RbxInstanceProperties {
|
||||
name: "ROOT".to_owned(),
|
||||
class_name: "ROOT".to_owned(),
|
||||
properties: Default::default(),
|
||||
},
|
||||
metadata: Default::default(),
|
||||
})
|
||||
RojoTree::new(InstanceSnapshot::new().name("ROOT").class_name("ROOT"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,29 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, VecDeque},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use rbx_dom_weak::{Descendants, RbxId, RbxInstance, RbxInstanceProperties, RbxTree, RbxValue};
|
||||
use rbx_dom_weak::{
|
||||
types::{Ref, Variant},
|
||||
Instance, InstanceBuilder, WeakDom,
|
||||
};
|
||||
|
||||
use crate::multimap::MultiMap;
|
||||
|
||||
use super::InstanceMetadata;
|
||||
use super::{InstanceMetadata, InstanceSnapshot};
|
||||
|
||||
/// An expanded variant of rbx_dom_weak's `RbxTree` that tracks additional
|
||||
/// An expanded variant of rbx_dom_weak's `WeakDom` that tracks additional
|
||||
/// metadata per instance that's Rojo-specific.
|
||||
///
|
||||
/// This tree is also optimized for doing fast incremental updates and patches.
|
||||
#[derive(Debug)]
|
||||
pub struct RojoTree {
|
||||
/// Contains the instances without their Rojo-specific metadata.
|
||||
inner: RbxTree,
|
||||
inner: WeakDom,
|
||||
|
||||
/// Metadata associated with each instance that is kept up-to-date with the
|
||||
/// set of actual instances.
|
||||
metadata_map: HashMap<RbxId, InstanceMetadata>,
|
||||
metadata_map: HashMap<Ref, InstanceMetadata>,
|
||||
|
||||
/// A multimap from source paths to all of the root instances that were
|
||||
/// constructed from that path.
|
||||
|
|
@ -29,31 +32,42 @@ pub struct RojoTree {
|
|||
/// value portion of the map is also a set in order to support the same path
|
||||
/// appearing multiple times in the same Rojo project. This is sometimes
|
||||
/// called "path aliasing" in various Rojo documentation.
|
||||
path_to_ids: MultiMap<PathBuf, RbxId>,
|
||||
path_to_ids: MultiMap<PathBuf, Ref>,
|
||||
}
|
||||
|
||||
impl RojoTree {
|
||||
pub fn new(root: InstancePropertiesWithMeta) -> RojoTree {
|
||||
pub fn new(snapshot: InstanceSnapshot) -> RojoTree {
|
||||
let root_builder = InstanceBuilder::new(snapshot.class_name.to_owned())
|
||||
.with_name(snapshot.name.to_owned())
|
||||
.with_properties(snapshot.properties);
|
||||
|
||||
let mut tree = RojoTree {
|
||||
inner: RbxTree::new(root.properties),
|
||||
inner: WeakDom::new(root_builder),
|
||||
metadata_map: HashMap::new(),
|
||||
path_to_ids: MultiMap::new(),
|
||||
};
|
||||
|
||||
tree.insert_metadata(tree.inner.get_root_id(), root.metadata);
|
||||
let root_ref = tree.inner.root_ref();
|
||||
|
||||
tree.insert_metadata(root_ref, snapshot.metadata);
|
||||
|
||||
for child in snapshot.children {
|
||||
tree.insert_instance(root_ref, child);
|
||||
}
|
||||
|
||||
tree
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &RbxTree {
|
||||
pub fn inner(&self) -> &WeakDom {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn get_root_id(&self) -> RbxId {
|
||||
self.inner.get_root_id()
|
||||
pub fn get_root_id(&self) -> Ref {
|
||||
self.inner.root_ref()
|
||||
}
|
||||
|
||||
pub fn get_instance(&self, id: RbxId) -> Option<InstanceWithMeta> {
|
||||
if let Some(instance) = self.inner.get_instance(id) {
|
||||
pub fn get_instance(&self, id: Ref) -> Option<InstanceWithMeta> {
|
||||
if let Some(instance) = self.inner.get_by_ref(id) {
|
||||
let metadata = self.metadata_map.get(&id).unwrap();
|
||||
|
||||
Some(InstanceWithMeta { instance, metadata })
|
||||
|
|
@ -62,8 +76,8 @@ impl RojoTree {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_instance_mut(&mut self, id: RbxId) -> Option<InstanceWithMetaMut> {
|
||||
if let Some(instance) = self.inner.get_instance_mut(id) {
|
||||
pub fn get_instance_mut(&mut self, id: Ref) -> Option<InstanceWithMetaMut> {
|
||||
if let Some(instance) = self.inner.get_by_ref_mut(id) {
|
||||
let metadata = self.metadata_map.get_mut(&id).unwrap();
|
||||
|
||||
Some(InstanceWithMetaMut { instance, metadata })
|
||||
|
|
@ -72,38 +86,38 @@ impl RojoTree {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn insert_instance(
|
||||
&mut self,
|
||||
properties: InstancePropertiesWithMeta,
|
||||
parent_id: RbxId,
|
||||
) -> RbxId {
|
||||
let id = self.inner.insert_instance(properties.properties, parent_id);
|
||||
self.insert_metadata(id, properties.metadata);
|
||||
id
|
||||
pub fn insert_instance(&mut self, parent_ref: Ref, snapshot: InstanceSnapshot) -> Ref {
|
||||
let builder = InstanceBuilder::new(snapshot.class_name.to_owned())
|
||||
.with_name(snapshot.name.to_owned())
|
||||
.with_properties(snapshot.properties);
|
||||
|
||||
let referent = self.inner.insert(parent_ref, builder);
|
||||
self.insert_metadata(referent, snapshot.metadata);
|
||||
|
||||
for child in snapshot.children {
|
||||
self.insert_instance(referent, child);
|
||||
}
|
||||
|
||||
referent
|
||||
}
|
||||
|
||||
pub fn remove_instance(&mut self, id: RbxId) -> Option<RojoTree> {
|
||||
if let Some(inner) = self.inner.remove_instance(id) {
|
||||
let mut metadata_map = HashMap::new();
|
||||
let mut path_to_ids = MultiMap::new();
|
||||
pub fn remove(&mut self, id: Ref) {
|
||||
let mut to_move = VecDeque::new();
|
||||
to_move.push_back(id);
|
||||
|
||||
self.move_metadata(id, &mut metadata_map, &mut path_to_ids);
|
||||
for instance in inner.descendants(id) {
|
||||
self.move_metadata(instance.get_id(), &mut metadata_map, &mut path_to_ids);
|
||||
while let Some(id) = to_move.pop_front() {
|
||||
self.remove_metadata(id);
|
||||
|
||||
if let Some(instance) = self.inner.get_by_ref(id) {
|
||||
to_move.extend(instance.children().iter().copied());
|
||||
}
|
||||
|
||||
Some(RojoTree {
|
||||
inner,
|
||||
metadata_map,
|
||||
path_to_ids,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
self.inner.destroy(id);
|
||||
}
|
||||
|
||||
/// Replaces the metadata associated with the given instance ID.
|
||||
pub fn update_metadata(&mut self, id: RbxId, metadata: InstanceMetadata) {
|
||||
pub fn update_metadata(&mut self, id: Ref, metadata: InstanceMetadata) {
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
match self.metadata_map.entry(id) {
|
||||
|
|
@ -131,22 +145,22 @@ impl RojoTree {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn descendants(&self, id: RbxId) -> RojoDescendants<'_> {
|
||||
RojoDescendants {
|
||||
inner: self.inner.descendants(id),
|
||||
tree: self,
|
||||
}
|
||||
pub fn descendants(&self, id: Ref) -> RojoDescendants<'_> {
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back(id);
|
||||
|
||||
RojoDescendants { queue, tree: self }
|
||||
}
|
||||
|
||||
pub fn get_ids_at_path(&self, path: &Path) -> &[RbxId] {
|
||||
pub fn get_ids_at_path(&self, path: &Path) -> &[Ref] {
|
||||
self.path_to_ids.get(path)
|
||||
}
|
||||
|
||||
pub fn get_metadata(&self, id: RbxId) -> Option<&InstanceMetadata> {
|
||||
pub fn get_metadata(&self, id: Ref) -> Option<&InstanceMetadata> {
|
||||
self.metadata_map.get(&id)
|
||||
}
|
||||
|
||||
fn insert_metadata(&mut self, id: RbxId, metadata: InstanceMetadata) {
|
||||
fn insert_metadata(&mut self, id: Ref, metadata: InstanceMetadata) {
|
||||
for path in &metadata.relevant_paths {
|
||||
self.path_to_ids.insert(path.clone(), id);
|
||||
}
|
||||
|
|
@ -156,25 +170,17 @@ impl RojoTree {
|
|||
|
||||
/// Moves the Rojo metadata from the instance with the given ID from this
|
||||
/// tree into some loose maps.
|
||||
fn move_metadata(
|
||||
&mut self,
|
||||
id: RbxId,
|
||||
metadata_map: &mut HashMap<RbxId, InstanceMetadata>,
|
||||
path_to_ids: &mut MultiMap<PathBuf, RbxId>,
|
||||
) {
|
||||
fn remove_metadata(&mut self, id: Ref) {
|
||||
let metadata = self.metadata_map.remove(&id).unwrap();
|
||||
|
||||
for path in &metadata.relevant_paths {
|
||||
self.path_to_ids.remove(path, id);
|
||||
path_to_ids.insert(path.clone(), id);
|
||||
}
|
||||
|
||||
metadata_map.insert(id, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RojoDescendants<'a> {
|
||||
inner: Descendants<'a>,
|
||||
queue: VecDeque<Ref>,
|
||||
tree: &'a RojoTree,
|
||||
}
|
||||
|
||||
|
|
@ -182,50 +188,43 @@ impl<'a> Iterator for RojoDescendants<'a> {
|
|||
type Item = InstanceWithMeta<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let instance = self.inner.next()?;
|
||||
let id = self.queue.pop_front()?;
|
||||
|
||||
let instance = self
|
||||
.tree
|
||||
.inner
|
||||
.get_by_ref(id)
|
||||
.expect("Instance did not exist");
|
||||
|
||||
let metadata = self
|
||||
.tree
|
||||
.get_metadata(instance.get_id())
|
||||
.get_metadata(instance.referent())
|
||||
.expect("Metadata did not exist for instance");
|
||||
|
||||
self.queue.extend(instance.children().iter().copied());
|
||||
|
||||
Some(InstanceWithMeta { instance, metadata })
|
||||
}
|
||||
}
|
||||
|
||||
/// RojoTree's equivalent of `RbxInstanceProperties`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InstancePropertiesWithMeta {
|
||||
pub properties: RbxInstanceProperties,
|
||||
pub metadata: InstanceMetadata,
|
||||
}
|
||||
|
||||
impl InstancePropertiesWithMeta {
|
||||
pub fn new(properties: RbxInstanceProperties, metadata: InstanceMetadata) -> Self {
|
||||
InstancePropertiesWithMeta {
|
||||
properties,
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// RojoTree's equivalent of `&'a RbxInstance`.
|
||||
/// RojoTree's equivalent of `&'a Instance`.
|
||||
///
|
||||
/// This has to be a value type for RojoTree because the instance and metadata
|
||||
/// are stored in different places. The mutable equivalent is
|
||||
/// `InstanceWithMetaMut`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct InstanceWithMeta<'a> {
|
||||
instance: &'a RbxInstance,
|
||||
instance: &'a Instance,
|
||||
metadata: &'a InstanceMetadata,
|
||||
}
|
||||
|
||||
impl<'a> InstanceWithMeta<'a> {
|
||||
pub fn id(&self) -> RbxId {
|
||||
self.instance.get_id()
|
||||
pub fn id(&self) -> Ref {
|
||||
self.instance.referent()
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<RbxId> {
|
||||
self.instance.get_parent_id()
|
||||
pub fn parent(&self) -> Ref {
|
||||
self.instance.parent()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'a str {
|
||||
|
|
@ -233,15 +232,15 @@ impl<'a> InstanceWithMeta<'a> {
|
|||
}
|
||||
|
||||
pub fn class_name(&self) -> &'a str {
|
||||
&self.instance.class_name
|
||||
&self.instance.class
|
||||
}
|
||||
|
||||
pub fn properties(&self) -> &'a HashMap<String, RbxValue> {
|
||||
pub fn properties(&self) -> &'a HashMap<String, Variant> {
|
||||
&self.instance.properties
|
||||
}
|
||||
|
||||
pub fn children(&self) -> &'a [RbxId] {
|
||||
self.instance.get_children_ids()
|
||||
pub fn children(&self) -> &'a [Ref] {
|
||||
self.instance.children()
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> &'a InstanceMetadata {
|
||||
|
|
@ -249,20 +248,20 @@ impl<'a> InstanceWithMeta<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// RojoTree's equivalent of `&'a mut RbxInstance`.
|
||||
/// RojoTree's equivalent of `&'a mut Instance`.
|
||||
///
|
||||
/// This has to be a value type for RojoTree because the instance and metadata
|
||||
/// are stored in different places. The immutable equivalent is
|
||||
/// `InstanceWithMeta`.
|
||||
#[derive(Debug)]
|
||||
pub struct InstanceWithMetaMut<'a> {
|
||||
instance: &'a mut RbxInstance,
|
||||
instance: &'a mut Instance,
|
||||
metadata: &'a mut InstanceMetadata,
|
||||
}
|
||||
|
||||
impl InstanceWithMetaMut<'_> {
|
||||
pub fn id(&self) -> RbxId {
|
||||
self.instance.get_id()
|
||||
pub fn id(&self) -> Ref {
|
||||
self.instance.referent()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
|
|
@ -274,23 +273,23 @@ impl InstanceWithMetaMut<'_> {
|
|||
}
|
||||
|
||||
pub fn class_name(&self) -> &str {
|
||||
&self.instance.class_name
|
||||
&self.instance.class
|
||||
}
|
||||
|
||||
pub fn class_name_mut(&mut self) -> &mut String {
|
||||
&mut self.instance.class_name
|
||||
&mut self.instance.class
|
||||
}
|
||||
|
||||
pub fn properties(&self) -> &HashMap<String, RbxValue> {
|
||||
pub fn properties(&self) -> &HashMap<String, Variant> {
|
||||
&self.instance.properties
|
||||
}
|
||||
|
||||
pub fn properties_mut(&mut self) -> &mut HashMap<String, RbxValue> {
|
||||
pub fn properties_mut(&mut self) -> &mut HashMap<String, Variant> {
|
||||
&mut self.instance.properties
|
||||
}
|
||||
|
||||
pub fn children(&self) -> &[RbxId] {
|
||||
self.instance.get_children_ids()
|
||||
pub fn children(&self) -> &[Ref] {
|
||||
self.instance.children()
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> &InstanceMetadata {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use std::{collections::BTreeMap, path::Path};
|
|||
use anyhow::Context;
|
||||
use maplit::hashmap;
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
use rbx_dom_weak::RbxValue;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
|
||||
|
|
@ -30,9 +29,7 @@ pub fn snapshot_csv(
|
|||
.name(instance_name)
|
||||
.class_name("LocalizationTable")
|
||||
.properties(hashmap! {
|
||||
"Contents".to_owned() => RbxValue::String {
|
||||
value: table_contents,
|
||||
},
|
||||
"Contents".to_owned() => table_contents.into(),
|
||||
})
|
||||
.metadata(
|
||||
InstanceMetadata::new()
|
||||
|
|
@ -41,8 +38,8 @@ pub fn snapshot_csv(
|
|||
);
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
||||
let mut metadata = AdjacentMetadata::from_slice(&meta_contents, &meta_path)?;
|
||||
metadata.apply_all(&mut snapshot);
|
||||
let mut metadata = AdjacentMetadata::from_slice(&meta_contents, meta_path)?;
|
||||
metadata.apply_all(&mut snapshot)?;
|
||||
}
|
||||
|
||||
Ok(Some(snapshot))
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ pub fn snapshot_dir(context: &InstanceContext, vfs: &Vfs, path: &Path) -> Snapsh
|
|||
);
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
||||
let mut metadata = DirectoryMetadata::from_slice(&meta_contents, &meta_path)?;
|
||||
metadata.apply_all(&mut snapshot);
|
||||
let mut metadata = DirectoryMetadata::from_slice(&meta_contents, meta_path)?;
|
||||
metadata.apply_all(&mut snapshot)?;
|
||||
}
|
||||
|
||||
Ok(Some(snapshot))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use std::path::Path;
|
|||
use anyhow::Context;
|
||||
use maplit::hashmap;
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
use rbx_dom_weak::RbxValue;
|
||||
|
||||
use crate::{
|
||||
lua_ast::{Expression, Statement},
|
||||
|
|
@ -26,9 +25,7 @@ pub fn snapshot_json(
|
|||
let as_lua = json_to_lua(value).to_string();
|
||||
|
||||
let properties = hashmap! {
|
||||
"Source".to_owned() => RbxValue::String {
|
||||
value: as_lua,
|
||||
},
|
||||
"Source".to_owned() => as_lua.into(),
|
||||
};
|
||||
|
||||
let meta_path = path.with_file_name(format!("{}.meta.json", instance_name));
|
||||
|
|
@ -45,8 +42,8 @@ pub fn snapshot_json(
|
|||
);
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
||||
let mut metadata = AdjacentMetadata::from_slice(&meta_contents, &meta_path)?;
|
||||
metadata.apply_all(&mut snapshot);
|
||||
let mut metadata = AdjacentMetadata::from_slice(&meta_contents, meta_path)?;
|
||||
metadata.apply_all(&mut snapshot)?;
|
||||
}
|
||||
|
||||
Ok(Some(snapshot))
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ use std::{borrow::Cow, collections::HashMap, path::Path};
|
|||
|
||||
use anyhow::Context;
|
||||
use memofs::Vfs;
|
||||
use rbx_dom_weak::UnresolvedRbxValue;
|
||||
use rbx_reflection::try_resolve_value;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::snapshot::{InstanceContext, InstanceSnapshot};
|
||||
use crate::{
|
||||
resolution::UnresolvedValue,
|
||||
snapshot::{InstanceContext, InstanceSnapshot},
|
||||
};
|
||||
|
||||
use super::middleware::SnapshotInstanceResult;
|
||||
|
||||
|
|
@ -20,7 +21,10 @@ pub fn snapshot_json_model(
|
|||
let instance: JsonModel = serde_json::from_slice(&contents)
|
||||
.with_context(|| format!("File is not a valid JSON model: {}", path.display()))?;
|
||||
|
||||
let mut snapshot = instance.core.into_snapshot(instance_name.to_owned());
|
||||
let mut snapshot = instance
|
||||
.core
|
||||
.into_snapshot(instance_name.to_owned())
|
||||
.with_context(|| format!("Could not load JSON model: {}", path.display()))?;
|
||||
|
||||
snapshot.metadata = snapshot
|
||||
.metadata
|
||||
|
|
@ -58,36 +62,32 @@ struct JsonModelCore {
|
|||
children: Vec<JsonModelInstance>,
|
||||
|
||||
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
|
||||
properties: HashMap<String, UnresolvedRbxValue>,
|
||||
properties: HashMap<String, UnresolvedValue>,
|
||||
}
|
||||
|
||||
impl JsonModelCore {
|
||||
fn into_snapshot(self, name: String) -> InstanceSnapshot {
|
||||
fn into_snapshot(self, name: String) -> anyhow::Result<InstanceSnapshot> {
|
||||
let class_name = self.class_name;
|
||||
|
||||
let children = self
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|child| child.core.into_snapshot(child.name))
|
||||
.collect();
|
||||
let mut children = Vec::with_capacity(self.children.len());
|
||||
for child in self.children {
|
||||
children.push(child.core.into_snapshot(child.name)?);
|
||||
}
|
||||
|
||||
let properties = self
|
||||
.properties
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
try_resolve_value(&class_name, &key, &value).map(|resolved| (key, resolved))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, _>>()
|
||||
.expect("TODO: Handle rbx_reflection errors");
|
||||
let mut properties = HashMap::with_capacity(self.properties.len());
|
||||
for (key, unresolved) in self.properties {
|
||||
let value = unresolved.resolve(&class_name, &key)?;
|
||||
properties.insert(key, value);
|
||||
}
|
||||
|
||||
InstanceSnapshot {
|
||||
Ok(InstanceSnapshot {
|
||||
snapshot_id: None,
|
||||
metadata: Default::default(),
|
||||
name: Cow::Owned(name),
|
||||
class_name: Cow::Owned(class_name),
|
||||
properties,
|
||||
children,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use std::{path::Path, str};
|
|||
use anyhow::Context;
|
||||
use maplit::hashmap;
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
use rbx_dom_weak::RbxValue;
|
||||
|
||||
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
|
||||
|
||||
|
|
@ -38,9 +37,7 @@ pub fn snapshot_lua(context: &InstanceContext, vfs: &Vfs, path: &Path) -> Snapsh
|
|||
.name(instance_name)
|
||||
.class_name(class_name)
|
||||
.properties(hashmap! {
|
||||
"Source".to_owned() => RbxValue::String {
|
||||
value: contents_str,
|
||||
},
|
||||
"Source".to_owned() => contents_str.into(),
|
||||
})
|
||||
.metadata(
|
||||
InstanceMetadata::new()
|
||||
|
|
@ -50,8 +47,8 @@ pub fn snapshot_lua(context: &InstanceContext, vfs: &Vfs, path: &Path) -> Snapsh
|
|||
);
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
||||
let mut metadata = AdjacentMetadata::from_slice(&meta_contents, &meta_path)?;
|
||||
metadata.apply_all(&mut snapshot);
|
||||
let mut metadata = AdjacentMetadata::from_slice(&meta_contents, meta_path)?;
|
||||
metadata.apply_all(&mut snapshot)?;
|
||||
}
|
||||
|
||||
Ok(Some(snapshot))
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
use std::{borrow::Cow, collections::HashMap, path::Path};
|
||||
use std::{borrow::Cow, collections::HashMap, path::PathBuf};
|
||||
|
||||
use anyhow::Context;
|
||||
use rbx_dom_weak::UnresolvedRbxValue;
|
||||
use rbx_reflection::try_resolve_value;
|
||||
use anyhow::{format_err, Context};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::snapshot::InstanceSnapshot;
|
||||
use crate::{resolution::UnresolvedValue, snapshot::InstanceSnapshot};
|
||||
|
||||
/// Represents metadata in a sibling file with the same basename.
|
||||
///
|
||||
|
|
@ -18,17 +16,23 @@ pub struct AdjacentMetadata {
|
|||
pub ignore_unknown_instances: Option<bool>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub properties: HashMap<String, UnresolvedRbxValue>,
|
||||
pub properties: HashMap<String, UnresolvedValue>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl AdjacentMetadata {
|
||||
pub fn from_slice(slice: &[u8], path: &Path) -> anyhow::Result<Self> {
|
||||
serde_json::from_slice(slice).with_context(|| {
|
||||
pub fn from_slice(slice: &[u8], path: PathBuf) -> anyhow::Result<Self> {
|
||||
let mut meta: Self = serde_json::from_slice(slice).with_context(|| {
|
||||
format!(
|
||||
"File contained malformed .meta.json data: {}",
|
||||
path.display()
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
meta.path = path;
|
||||
Ok(meta)
|
||||
}
|
||||
|
||||
pub fn apply_ignore_unknown_instances(&mut self, snapshot: &mut InstanceSnapshot) {
|
||||
|
|
@ -37,23 +41,24 @@ impl AdjacentMetadata {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn apply_properties(&mut self, snapshot: &mut InstanceSnapshot) {
|
||||
let class_name = &snapshot.class_name;
|
||||
pub fn apply_properties(&mut self, snapshot: &mut InstanceSnapshot) -> anyhow::Result<()> {
|
||||
let path = &self.path;
|
||||
|
||||
let source_properties = self.properties.drain().map(|(key, value)| {
|
||||
try_resolve_value(class_name, &key, &value)
|
||||
.map(|resolved| (key, resolved))
|
||||
.expect("TODO: Handle rbx_reflection errors")
|
||||
});
|
||||
for (key, unresolved) in self.properties.drain() {
|
||||
let value = unresolved
|
||||
.resolve(&snapshot.class_name, &key)
|
||||
.with_context(|| format!("error applying meta file {}", path.display()))?;
|
||||
|
||||
for (key, value) in source_properties {
|
||||
snapshot.properties.insert(key, value);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_all(&mut self, snapshot: &mut InstanceSnapshot) {
|
||||
pub fn apply_all(&mut self, snapshot: &mut InstanceSnapshot) -> anyhow::Result<()> {
|
||||
self.apply_ignore_unknown_instances(snapshot);
|
||||
self.apply_properties(snapshot);
|
||||
self.apply_properties(snapshot)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Add method to allow selectively applying parts of metadata and
|
||||
|
|
@ -71,37 +76,50 @@ pub struct DirectoryMetadata {
|
|||
pub ignore_unknown_instances: Option<bool>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub properties: HashMap<String, UnresolvedRbxValue>,
|
||||
pub properties: HashMap<String, UnresolvedValue>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub class_name: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl DirectoryMetadata {
|
||||
pub fn from_slice(slice: &[u8], path: &Path) -> anyhow::Result<Self> {
|
||||
serde_json::from_slice(slice).with_context(|| {
|
||||
pub fn from_slice(slice: &[u8], path: PathBuf) -> anyhow::Result<Self> {
|
||||
let mut meta: Self = serde_json::from_slice(slice).with_context(|| {
|
||||
format!(
|
||||
"File contained malformed init.meta.json data: {}",
|
||||
path.display()
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
meta.path = path;
|
||||
Ok(meta)
|
||||
}
|
||||
|
||||
pub fn apply_all(&mut self, snapshot: &mut InstanceSnapshot) {
|
||||
pub fn apply_all(&mut self, snapshot: &mut InstanceSnapshot) -> anyhow::Result<()> {
|
||||
self.apply_ignore_unknown_instances(snapshot);
|
||||
self.apply_class_name(snapshot);
|
||||
self.apply_properties(snapshot);
|
||||
self.apply_class_name(snapshot)?;
|
||||
self.apply_properties(snapshot)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_class_name(&mut self, snapshot: &mut InstanceSnapshot) {
|
||||
fn apply_class_name(&mut self, snapshot: &mut InstanceSnapshot) -> anyhow::Result<()> {
|
||||
if let Some(class_name) = self.class_name.take() {
|
||||
if snapshot.class_name != "Folder" {
|
||||
// TODO: Turn into error type
|
||||
panic!("className in init.meta.json can only be specified if the affected directory would turn into a Folder instance.");
|
||||
return Err(format_err!(
|
||||
"className in init.meta.json can only be specified if the \
|
||||
affected directory would turn into a Folder instance."
|
||||
));
|
||||
}
|
||||
|
||||
snapshot.class_name = Cow::Owned(class_name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_ignore_unknown_instances(&mut self, snapshot: &mut InstanceSnapshot) {
|
||||
|
|
@ -110,17 +128,17 @@ impl DirectoryMetadata {
|
|||
}
|
||||
}
|
||||
|
||||
fn apply_properties(&mut self, snapshot: &mut InstanceSnapshot) {
|
||||
let class_name = &snapshot.class_name;
|
||||
fn apply_properties(&mut self, snapshot: &mut InstanceSnapshot) -> anyhow::Result<()> {
|
||||
let path = &self.path;
|
||||
|
||||
let source_properties = self.properties.drain().map(|(key, value)| {
|
||||
try_resolve_value(class_name, &key, &value)
|
||||
.map(|resolved| (key, resolved))
|
||||
.expect("TODO: Handle rbx_reflection errors")
|
||||
});
|
||||
for (key, unresolved) in self.properties.drain() {
|
||||
let value = unresolved
|
||||
.resolve(&snapshot.class_name, &key)
|
||||
.with_context(|| format!("error applying meta file {}", path.display()))?;
|
||||
|
||||
for (key, value) in source_properties {
|
||||
snapshot.properties.insert(key, value);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{borrow::Cow, collections::HashMap, path::Path};
|
|||
|
||||
use anyhow::Context;
|
||||
use memofs::Vfs;
|
||||
use rbx_reflection::{get_class_descriptor, try_resolve_value};
|
||||
use rbx_reflection::ClassTag;
|
||||
|
||||
use crate::{
|
||||
project::{Project, ProjectNode},
|
||||
|
|
@ -140,9 +140,9 @@ pub fn snapshot_project_node(
|
|||
// Members of DataModel with names that match known services are
|
||||
// probably supposed to be those services.
|
||||
|
||||
let descriptor = get_class_descriptor(&name)?;
|
||||
let descriptor = rbx_reflection_database::get().classes.get(&name)?;
|
||||
|
||||
if descriptor.is_service() {
|
||||
if descriptor.tags.contains(&ClassTag::Service) {
|
||||
return Some(name.clone());
|
||||
}
|
||||
} else if parent_class == "StarterPlayer" {
|
||||
|
|
@ -171,11 +171,18 @@ pub fn snapshot_project_node(
|
|||
}
|
||||
}
|
||||
|
||||
for (key, value) in &node.properties {
|
||||
let resolved_value = try_resolve_value(&class_name, key, value)
|
||||
.expect("TODO: Properly handle value resolution errors");
|
||||
for (key, unresolved) in &node.properties {
|
||||
let value = unresolved
|
||||
.clone()
|
||||
.resolve(&class_name, key)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Unresolvable property in project at path {}",
|
||||
project_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
properties.insert(key.clone(), resolved_value);
|
||||
properties.insert(key.clone(), value);
|
||||
}
|
||||
|
||||
// If the user specified $ignoreUnknownInstances, overwrite the existing
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use std::{collections::HashMap, path::Path};
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
use memofs::Vfs;
|
||||
use rbx_dom_weak::{RbxInstanceProperties, RbxTree};
|
||||
|
||||
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
|
||||
|
||||
|
|
@ -14,18 +13,11 @@ pub fn snapshot_rbxm(
|
|||
path: &Path,
|
||||
instance_name: &str,
|
||||
) -> SnapshotInstanceResult {
|
||||
let mut temp_tree = RbxTree::new(RbxInstanceProperties {
|
||||
name: "DataModel".to_owned(),
|
||||
class_name: "DataModel".to_owned(),
|
||||
properties: HashMap::new(),
|
||||
});
|
||||
|
||||
let root_id = temp_tree.get_root_id();
|
||||
rbx_binary::decode(&mut temp_tree, root_id, vfs.read(path)?.as_slice())
|
||||
let temp_tree = rbx_binary::from_reader_default(vfs.read(path)?.as_slice())
|
||||
.with_context(|| format!("Malformed rbxm file: {}", path.display()))?;
|
||||
|
||||
let root_instance = temp_tree.get_instance(root_id).unwrap();
|
||||
let children = root_instance.get_children_ids();
|
||||
let root_instance = temp_tree.root();
|
||||
let children = root_instance.children();
|
||||
|
||||
if children.len() == 1 {
|
||||
let snapshot = InstanceSnapshot::from_tree(&temp_tree, children[0])
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ pub fn snapshot_rbxmx(
|
|||
let temp_tree = rbx_xml::from_reader(vfs.read(path)?.as_slice(), options)
|
||||
.with_context(|| format!("Malformed rbxm file: {}", path.display()))?;
|
||||
|
||||
let root_instance = temp_tree.get_instance(temp_tree.get_root_id()).unwrap();
|
||||
let children = root_instance.get_children_ids();
|
||||
let root_instance = temp_tree.root();
|
||||
let children = root_instance.children();
|
||||
|
||||
if children.len() == 1 {
|
||||
let snapshot = InstanceSnapshot::from_tree(&temp_tree, children[0])
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ name: foo
|
|||
class_name: IntValue
|
||||
properties:
|
||||
Value:
|
||||
Type: Int32
|
||||
Type: Int64
|
||||
Value: 5
|
||||
children:
|
||||
- snapshot_id: ~
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use std::{path::Path, str};
|
|||
use anyhow::Context;
|
||||
use maplit::hashmap;
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
use rbx_dom_weak::RbxValue;
|
||||
|
||||
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
|
||||
|
||||
|
|
@ -21,9 +20,7 @@ pub fn snapshot_txt(
|
|||
.to_owned();
|
||||
|
||||
let properties = hashmap! {
|
||||
"Value".to_owned() => RbxValue::String {
|
||||
value: contents_str,
|
||||
},
|
||||
"Value".to_owned() => contents_str.into(),
|
||||
};
|
||||
|
||||
let meta_path = path.with_file_name(format!("{}.meta.json", instance_name));
|
||||
|
|
@ -40,8 +37,8 @@ pub fn snapshot_txt(
|
|||
);
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
||||
let mut metadata = AdjacentMetadata::from_slice(&meta_contents, &meta_path)?;
|
||||
metadata.apply_all(&mut snapshot);
|
||||
let mut metadata = AdjacentMetadata::from_slice(&meta_contents, meta_path)?;
|
||||
metadata.apply_all(&mut snapshot)?;
|
||||
}
|
||||
|
||||
Ok(Some(snapshot))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use rbx_dom_weak::{RbxId, RbxValue};
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
use rojo_insta_ext::RedactionMap;
|
||||
use serde::Serialize;
|
||||
|
||||
|
|
@ -29,15 +29,15 @@ pub fn intern_tree(tree: &RojoTree, redactions: &mut RedactionMap) {
|
|||
/// Copy of data from RojoTree in the right shape to have useful snapshots.
|
||||
#[derive(Debug, Serialize)]
|
||||
struct InstanceView {
|
||||
id: RbxId,
|
||||
id: Ref,
|
||||
name: String,
|
||||
class_name: String,
|
||||
properties: HashMap<String, RbxValue>,
|
||||
properties: HashMap<String, Variant>,
|
||||
metadata: InstanceMetadata,
|
||||
children: Vec<InstanceView>,
|
||||
}
|
||||
|
||||
fn extract_instance_view(tree: &RojoTree, id: RbxId) -> InstanceView {
|
||||
fn extract_instance_view(tree: &RojoTree, id: Ref) -> InstanceView {
|
||||
let instance = tree.get_instance(id).unwrap();
|
||||
|
||||
InstanceView {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
//! Defines Rojo's HTTP API, all under /api. These endpoints generally return
|
||||
//! JSON.
|
||||
|
||||
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
|
||||
use std::{collections::HashMap, fs, path::PathBuf, str::FromStr, sync::Arc};
|
||||
|
||||
use futures::{Future, Stream};
|
||||
|
||||
use hyper::{service::Service, Body, Method, Request, StatusCode};
|
||||
use rbx_dom_weak::RbxId;
|
||||
use rbx_dom_weak::types::Ref;
|
||||
|
||||
use crate::{
|
||||
serve_session::ServeSession,
|
||||
|
|
@ -200,11 +200,11 @@ impl ApiService {
|
|||
|
||||
fn handle_api_read(&self, request: Request<Body>) -> <Self as Service>::Future {
|
||||
let argument = &request.uri().path()["/api/read/".len()..];
|
||||
let requested_ids: Option<Vec<RbxId>> = argument.split(',').map(RbxId::parse_str).collect();
|
||||
let requested_ids: Result<Vec<Ref>, _> = argument.split(',').map(Ref::from_str).collect();
|
||||
|
||||
let requested_ids = match requested_ids {
|
||||
Some(ids) => ids,
|
||||
None => {
|
||||
Ok(ids) => ids,
|
||||
Err(_) => {
|
||||
return json(
|
||||
ErrorResponse::bad_request("Malformed ID list"),
|
||||
StatusCode::BAD_REQUEST,
|
||||
|
|
@ -239,9 +239,9 @@ impl ApiService {
|
|||
/// Open a script with the given ID in the user's default text editor.
|
||||
fn handle_api_open(&self, request: Request<Body>) -> <Self as Service>::Future {
|
||||
let argument = &request.uri().path()["/api/open/".len()..];
|
||||
let requested_id = match RbxId::parse_str(argument) {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
let requested_id = match Ref::from_str(argument) {
|
||||
Ok(id) => id,
|
||||
Err(_) => {
|
||||
return json(
|
||||
ErrorResponse::bad_request("Invalid instance ID"),
|
||||
StatusCode::BAD_REQUEST,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::{
|
|||
collections::{HashMap, HashSet},
|
||||
};
|
||||
|
||||
use rbx_dom_weak::{RbxId, RbxValue};
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -23,22 +23,22 @@ pub const PROTOCOL_VERSION: u64 = 3;
|
|||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SubscribeMessage<'a> {
|
||||
pub removed: Vec<RbxId>,
|
||||
pub added: HashMap<RbxId, Instance<'a>>,
|
||||
pub removed: Vec<Ref>,
|
||||
pub added: HashMap<Ref, Instance<'a>>,
|
||||
pub updated: Vec<InstanceUpdate>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InstanceUpdate {
|
||||
pub id: RbxId,
|
||||
pub id: Ref,
|
||||
pub changed_name: Option<String>,
|
||||
pub changed_class_name: Option<String>,
|
||||
|
||||
// TODO: Transform from HashMap<String, Option<_>> to something else, since
|
||||
// null will get lost when decoding from JSON in some languages.
|
||||
#[serde(default)]
|
||||
pub changed_properties: HashMap<String, Option<RbxValue>>,
|
||||
pub changed_properties: HashMap<String, Option<Variant>>,
|
||||
pub changed_metadata: Option<InstanceMetadata>,
|
||||
}
|
||||
|
||||
|
|
@ -59,23 +59,36 @@ impl InstanceMetadata {
|
|||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Instance<'a> {
|
||||
pub id: RbxId,
|
||||
pub parent: Option<RbxId>,
|
||||
pub id: Ref,
|
||||
pub parent: Ref,
|
||||
pub name: Cow<'a, str>,
|
||||
pub class_name: Cow<'a, str>,
|
||||
pub properties: Cow<'a, HashMap<String, RbxValue>>,
|
||||
pub children: Cow<'a, [RbxId]>,
|
||||
pub properties: HashMap<String, Cow<'a, Variant>>,
|
||||
pub children: Cow<'a, [Ref]>,
|
||||
pub metadata: Option<InstanceMetadata>,
|
||||
}
|
||||
|
||||
impl<'a> Instance<'a> {
|
||||
pub(crate) fn from_rojo_instance(source: InstanceWithMeta<'_>) -> Instance<'_> {
|
||||
let properties = source
|
||||
.properties()
|
||||
.iter()
|
||||
.filter_map(|(key, value)| {
|
||||
// SharedString values can't be serialized via Serde
|
||||
if matches!(value, Variant::SharedString(_)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((key.clone(), Cow::Borrowed(value)))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Instance {
|
||||
id: source.id(),
|
||||
parent: source.parent(),
|
||||
name: Cow::Borrowed(source.name()),
|
||||
class_name: Cow::Borrowed(source.class_name()),
|
||||
properties: Cow::Borrowed(source.properties()),
|
||||
properties,
|
||||
children: Cow::Borrowed(source.children()),
|
||||
metadata: Some(InstanceMetadata::from_rojo_metadata(source.metadata())),
|
||||
}
|
||||
|
|
@ -91,7 +104,7 @@ pub struct ServerInfoResponse {
|
|||
pub protocol_version: u64,
|
||||
pub project_name: String,
|
||||
pub expected_place_ids: Option<HashSet<u64>>,
|
||||
pub root_instance_id: RbxId,
|
||||
pub root_instance_id: Ref,
|
||||
}
|
||||
|
||||
/// Response body from /api/read/{id}
|
||||
|
|
@ -100,17 +113,17 @@ pub struct ServerInfoResponse {
|
|||
pub struct ReadResponse<'a> {
|
||||
pub session_id: SessionId,
|
||||
pub message_cursor: u32,
|
||||
pub instances: HashMap<RbxId, Instance<'a>>,
|
||||
pub instances: HashMap<Ref, Instance<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WriteRequest {
|
||||
pub session_id: SessionId,
|
||||
pub removed: Vec<RbxId>,
|
||||
pub removed: Vec<Ref>,
|
||||
|
||||
#[serde(default)]
|
||||
pub added: HashMap<RbxId, ()>,
|
||||
pub added: HashMap<Ref, ()>,
|
||||
pub updated: Vec<InstanceUpdate>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::{borrow::Cow, sync::Arc, time::Duration};
|
|||
use futures::{future, Future};
|
||||
use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode};
|
||||
use maplit::hashmap;
|
||||
use rbx_dom_weak::{RbxId, RbxValue};
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
use ritz::{html, Fragment, HtmlContent, HtmlSelfClosingTag};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -93,7 +93,7 @@ impl UiService {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
fn instance(tree: &RojoTree, id: RbxId) -> HtmlContent<'_> {
|
||||
fn instance(tree: &RojoTree, id: Ref) -> HtmlContent<'_> {
|
||||
let instance = tree.get_instance(id).unwrap();
|
||||
let children_list: Vec<_> = instance
|
||||
.children()
|
||||
|
|
@ -126,7 +126,7 @@ impl UiService {
|
|||
.map(|(key, value)| {
|
||||
html! {
|
||||
<div class="instance-property" title={ Self::display_value(value) }>
|
||||
{ key.clone() } ": " { format!("{:?}", value.get_type()) }
|
||||
{ key.clone() } ": " { format!("{:?}", value.ty()) }
|
||||
</div>
|
||||
}
|
||||
})
|
||||
|
|
@ -198,7 +198,7 @@ impl UiService {
|
|||
|
||||
html! {
|
||||
<div class="instance">
|
||||
<label class="instance-title" for={ format!("instance-{}", id) }>
|
||||
<label class="instance-title" for={ format!("instance-{:?}", id) }>
|
||||
{ instance.name().to_owned() }
|
||||
{ class_name_specifier }
|
||||
</label>
|
||||
|
|
@ -209,10 +209,10 @@ impl UiService {
|
|||
}
|
||||
}
|
||||
|
||||
fn display_value(value: &RbxValue) -> String {
|
||||
fn display_value(value: &Variant) -> String {
|
||||
match value {
|
||||
RbxValue::String { value } => value.clone(),
|
||||
RbxValue::Bool { value } => value.to_string(),
|
||||
Variant::String(value) => value.clone(),
|
||||
Variant::Bool(value) => value.to_string(),
|
||||
_ => format!("{:?}", value),
|
||||
}
|
||||
}
|
||||
|
|
@ -288,14 +288,14 @@ impl UiService {
|
|||
struct ExpandableSection<'a> {
|
||||
title: &'a str,
|
||||
class_name: &'a str,
|
||||
id: RbxId,
|
||||
id: Ref,
|
||||
expanded: bool,
|
||||
content: HtmlContent<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ExpandableSection<'a> {
|
||||
fn render(self) -> HtmlContent<'a> {
|
||||
let input_id = format!("{}-{}", self.class_name, self.id);
|
||||
let input_id = format!("{}-{:?}", self.class_name, self.id);
|
||||
|
||||
// We need to specify this input manually because Ritz doesn't have
|
||||
// support for conditional attributes like `checked`.
|
||||
|
|
|
|||
12
test-projects/enums/default.project.json
Normal file
12
test-projects/enums/default.project.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "enums",
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
|
||||
"Lighting": {
|
||||
"$properties": {
|
||||
"Technology": "Voxel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
{
|
||||
"name": "unions",
|
||||
"tree": {
|
||||
"$path": "src"
|
||||
"$className": "DataModel",
|
||||
|
||||
"Workspace": {
|
||||
"TwoParts": {
|
||||
"$path": "src"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use rbx_dom_weak::RbxId;
|
||||
use rbx_dom_weak::types::Ref;
|
||||
use serde::Serialize;
|
||||
|
||||
use librojo::web_api::{Instance, InstanceUpdate, ReadResponse, SubscribeResponse};
|
||||
|
|
@ -31,8 +31,8 @@ pub trait Internable<T> {
|
|||
fn intern(&self, redactions: &mut RedactionMap, extra: T);
|
||||
}
|
||||
|
||||
impl Internable<RbxId> for ReadResponse<'_> {
|
||||
fn intern(&self, redactions: &mut RedactionMap, root_id: RbxId) {
|
||||
impl Internable<Ref> for ReadResponse<'_> {
|
||||
fn intern(&self, redactions: &mut RedactionMap, root_id: Ref) {
|
||||
redactions.intern(root_id);
|
||||
|
||||
let root_instance = self.instances.get(&root_id).unwrap();
|
||||
|
|
@ -43,12 +43,8 @@ impl Internable<RbxId> for ReadResponse<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Internable<&'a HashMap<RbxId, Instance<'_>>> for Instance<'a> {
|
||||
fn intern(
|
||||
&self,
|
||||
redactions: &mut RedactionMap,
|
||||
other_instances: &HashMap<RbxId, Instance<'_>>,
|
||||
) {
|
||||
impl<'a> Internable<&'a HashMap<Ref, Instance<'_>>> for Instance<'a> {
|
||||
fn intern(&self, redactions: &mut RedactionMap, other_instances: &HashMap<Ref, Instance<'_>>) {
|
||||
redactions.intern(self.id);
|
||||
|
||||
for child_id in self.children.iter() {
|
||||
|
|
@ -75,7 +71,7 @@ fn intern_instance_updates(redactions: &mut RedactionMap, updates: &[InstanceUpd
|
|||
|
||||
fn intern_instance_additions(
|
||||
redactions: &mut RedactionMap,
|
||||
additions: &HashMap<RbxId, Instance<'_>>,
|
||||
additions: &HashMap<Ref, Instance<'_>>,
|
||||
) {
|
||||
// This method redacts in a deterministic order from a HashMap by collecting
|
||||
// all of the instances that are direct children of instances we've already
|
||||
|
|
@ -83,7 +79,7 @@ fn intern_instance_additions(
|
|||
let mut added_roots = Vec::new();
|
||||
|
||||
for (id, added) in additions {
|
||||
let parent_id = added.parent.unwrap();
|
||||
let parent_id = added.parent;
|
||||
let parent_redacted = redactions.get_redacted_value(parent_id);
|
||||
|
||||
// Here, we assume that instances are only added to other instances that
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use rbx_dom_weak::RbxId;
|
||||
use rbx_dom_weak::types::Ref;
|
||||
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ impl TestServeSession {
|
|||
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
|
||||
}
|
||||
|
||||
pub fn get_api_read(&self, id: RbxId) -> Result<ReadResponse, reqwest::Error> {
|
||||
pub fn get_api_read(&self, id: Ref) -> Result<ReadResponse, reqwest::Error> {
|
||||
let url = format!("http://localhost:{}/api/read/{}", self.port, id);
|
||||
let body = reqwest::get(&url)?.text()?;
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ gen_build_tests! {
|
|||
server_init,
|
||||
txt,
|
||||
txt_in_folder,
|
||||
unresolved_values,
|
||||
}
|
||||
|
||||
fn run_build_test(test_name: &str) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue