mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Add default properties caching and update matching logic
- Implemented a caching mechanism for default properties per class in `rbx_dom_lua/init.lua`, allowing for efficient retrieval of encoded default values, including inherited defaults. - Updated the matching logic in `Reconciler/matching.lua` to account for properties present only on the Studio side, ensuring accurate comparisons against default values during reconciliation. - Added new test cases to validate the handling of default-omitted properties, enhancing the robustness of the reconciliation process. These changes improve the efficiency and accuracy of property matching, addressing issues related to default values in the reconciliation workflow.
This commit is contained in:
parent
e54f5ef4e3
commit
9c6966acff
30 changed files with 1648 additions and 0 deletions
|
|
@ -6,6 +6,43 @@ local PropertyDescriptor = require(script.PropertyDescriptor)
|
|||
-- Returns nil if the class doesn't exist in the database
|
||||
-- The properties table maps property names to metadata (scriptability, serialization status)
|
||||
-- To get a full PropertyDescriptor for encoding, use findCanonicalPropertyDescriptor
|
||||
-- Cache for default properties per class (includes inherited defaults).
|
||||
local defaultPropertiesCache: { [string]: { [string]: any } } = {}
|
||||
|
||||
-- Returns a table mapping property names to their encoded default values
|
||||
-- for a given class, including inherited defaults from parent classes.
|
||||
-- Subclass overrides take precedence. Results are cached per class.
|
||||
local function findDefaultProperties(className: string): { [string]: any }
|
||||
local cached = defaultPropertiesCache[className]
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
|
||||
local defaults: { [string]: any } = {}
|
||||
local currentClassName: string? = className
|
||||
|
||||
repeat
|
||||
local currentClass = database.Classes[currentClassName]
|
||||
if currentClass == nil then
|
||||
break
|
||||
end
|
||||
|
||||
local classDefaults = currentClass.DefaultProperties
|
||||
if classDefaults then
|
||||
for propName, encodedValue in classDefaults do
|
||||
if defaults[propName] == nil then
|
||||
defaults[propName] = encodedValue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
currentClassName = currentClass.Superclass
|
||||
until currentClassName == nil
|
||||
|
||||
defaultPropertiesCache[className] = defaults
|
||||
return defaults
|
||||
end
|
||||
|
||||
local function findClassDescriptor(className)
|
||||
local classData = database.Classes[className]
|
||||
if classData == nil then
|
||||
|
|
@ -111,6 +148,7 @@ return {
|
|||
writeProperty = writeProperty,
|
||||
findCanonicalPropertyDescriptor = findCanonicalPropertyDescriptor,
|
||||
findClassDescriptor = findClassDescriptor,
|
||||
findDefaultProperties = findDefaultProperties,
|
||||
Error = Error,
|
||||
EncodedValue = require(script.EncodedValue),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -330,6 +330,48 @@ function Matching._countOwnDiffs(vInst: any, studioInstance: Instance): number
|
|||
end
|
||||
end
|
||||
|
||||
-- Properties present only on the Studio side (not in virtual).
|
||||
-- Syncback strips default-valued properties from model files, so virtual
|
||||
-- instances may omit properties that Studio has at non-default values.
|
||||
-- Without this check, a virtual instance missing e.g. Face scores equally
|
||||
-- against all Studio instances in the group, causing wrong pairings.
|
||||
local defaults = RbxDom.findDefaultProperties(vInst.ClassName)
|
||||
if defaults then
|
||||
for propName, encodedDefault in defaults do
|
||||
if propName == "Tags" or propName == "Attributes" then
|
||||
continue
|
||||
end
|
||||
|
||||
-- Skip properties already compared from the virtual side
|
||||
if vProps and vProps[propName] ~= nil then
|
||||
continue
|
||||
end
|
||||
|
||||
-- Skip Ref/UniqueId types (not comparable during hydration)
|
||||
local ty = next(encodedDefault)
|
||||
if ty == "Ref" or ty == "UniqueId" then
|
||||
continue
|
||||
end
|
||||
|
||||
-- pcall guards against unsupported types (e.g. SharedString)
|
||||
local pcallOk, decodeOk, defaultValue = pcall(RbxDom.EncodedValue.decode, encodedDefault)
|
||||
if not pcallOk or not decodeOk then
|
||||
continue
|
||||
end
|
||||
|
||||
local readOk, studioValue = pcall(function()
|
||||
return (studioInstance :: any)[propName]
|
||||
end)
|
||||
if not readOk then
|
||||
continue
|
||||
end
|
||||
|
||||
if not trueEquals(defaultValue, studioValue) then
|
||||
cost += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Tags: count individual adds/removes
|
||||
local vTags = vInst.Properties and vInst.Properties.Tags
|
||||
if vTags then
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
className: "UIAspectRatioConstraint"
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
className: "ImageLabel",
|
||||
properties: {
|
||||
AnchorPoint: [
|
||||
0.5,
|
||||
0
|
||||
],
|
||||
BackgroundColor3: [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
BackgroundTransparency: 1,
|
||||
BorderColor3: [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
BorderSizePixel: 0,
|
||||
ImageContent: "rbxassetid://131974996946194",
|
||||
Position: {
|
||||
UDim2: [
|
||||
[
|
||||
0.5,
|
||||
0
|
||||
],
|
||||
[
|
||||
0.5,
|
||||
0
|
||||
]
|
||||
]
|
||||
},
|
||||
Size: {
|
||||
UDim2: [
|
||||
[
|
||||
0.699999988079071,
|
||||
0
|
||||
],
|
||||
[
|
||||
0.699999988079071,
|
||||
0
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
className: "UIGradient",
|
||||
properties: {
|
||||
Color: {
|
||||
ColorSequence: {
|
||||
keypoints: [
|
||||
{
|
||||
color: [
|
||||
0.1882352977991104,
|
||||
0.11372549086809158,
|
||||
1
|
||||
],
|
||||
time: 0
|
||||
},
|
||||
{
|
||||
color: [
|
||||
0.5490196347236633,
|
||||
0,
|
||||
1
|
||||
],
|
||||
time: 0.608996570110321
|
||||
},
|
||||
{
|
||||
color: [
|
||||
0.501960813999176,
|
||||
0,
|
||||
0.9176470637321472
|
||||
],
|
||||
time: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
Rotation: -90
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
className: "UIStroke",
|
||||
properties: {
|
||||
Thickness: 2
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
className: "TextLabel",
|
||||
properties: {
|
||||
AnchorPoint: [
|
||||
0.5,
|
||||
0
|
||||
],
|
||||
BackgroundColor3: [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
BackgroundTransparency: 1,
|
||||
BorderColor3: [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
BorderSizePixel: 0,
|
||||
FontFace: {
|
||||
cachedFaceId: null,
|
||||
family: "rbxasset://fonts/families/LuckiestGuy.json",
|
||||
style: "Normal",
|
||||
weight: "Regular"
|
||||
},
|
||||
Position: {
|
||||
UDim2: [
|
||||
[
|
||||
0.5,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
0
|
||||
]
|
||||
]
|
||||
},
|
||||
Size: {
|
||||
UDim2: [
|
||||
[
|
||||
1,
|
||||
0
|
||||
],
|
||||
[
|
||||
0.5,
|
||||
0
|
||||
]
|
||||
]
|
||||
},
|
||||
Text: "UFO",
|
||||
TextColor3: [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
TextScaled: true,
|
||||
TextSize: 14,
|
||||
TextWrapped: true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
className: "Frame",
|
||||
properties: {
|
||||
AnchorPoint: [
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
BackgroundColor3: [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
BackgroundTransparency: 1,
|
||||
BorderColor3: [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
BorderSizePixel: 0,
|
||||
Position: {
|
||||
UDim2: [
|
||||
[
|
||||
0.5,
|
||||
0
|
||||
],
|
||||
[
|
||||
0.5,
|
||||
0
|
||||
]
|
||||
]
|
||||
},
|
||||
Size: {
|
||||
UDim2: [
|
||||
[
|
||||
1,
|
||||
0
|
||||
],
|
||||
[
|
||||
1,
|
||||
0
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
className: "BillboardGui",
|
||||
properties: {
|
||||
Active: true,
|
||||
LightInfluence: 1,
|
||||
ResetOnSpawn: false,
|
||||
Size: {
|
||||
UDim2: [
|
||||
[
|
||||
60,
|
||||
0
|
||||
],
|
||||
[
|
||||
40,
|
||||
0
|
||||
]
|
||||
]
|
||||
},
|
||||
StudsOffset: [
|
||||
0,
|
||||
75,
|
||||
0
|
||||
],
|
||||
ZIndexBehavior: "Sibling"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
attributes: {
|
||||
Rojo_Ref_SoundGroup: "ServerStorage/__server_muted.model.json5"
|
||||
},
|
||||
className: "Sound",
|
||||
properties: {
|
||||
AudioContent: "rbxassetid://9120549564",
|
||||
Looped: true,
|
||||
Playing: true,
|
||||
SourceAssetId: 9120549564,
|
||||
Tags: [
|
||||
"SoundGroupEffects"
|
||||
],
|
||||
Volume: 3
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
className: "Part",
|
||||
properties: {
|
||||
Anchored: true,
|
||||
BottomSurface: "Smooth",
|
||||
CFrame: [
|
||||
789.7578125,
|
||||
37.375,
|
||||
0.00000762939453125,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
CanCollide: false,
|
||||
Size: [
|
||||
25.515625,
|
||||
70.75,
|
||||
260
|
||||
],
|
||||
TopSurface: "Smooth",
|
||||
Transparency: 1
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
className: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.41568630933761597,
|
||||
0,
|
||||
1
|
||||
],
|
||||
Face: "Top",
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10025668350",
|
||||
Transparency: 0.6000000238418579
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.41568630933761597,
|
||||
0,
|
||||
1
|
||||
],
|
||||
Face: "Right",
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10025668350",
|
||||
Transparency: 0.6000000238418579
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.40000003576278687,
|
||||
0,
|
||||
1
|
||||
],
|
||||
Face: "Right",
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10023635072",
|
||||
Transparency: 0.6499999761581421
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.40000003576278687,
|
||||
0,
|
||||
1
|
||||
],
|
||||
Face: "Top",
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10023635072",
|
||||
Transparency: 0.6499999761581421
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.41568630933761597,
|
||||
0,
|
||||
1
|
||||
],
|
||||
Face: "Left",
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10025668350",
|
||||
Transparency: 0.6000000238418579
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.41568630933761597,
|
||||
0,
|
||||
1
|
||||
],
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10025668350",
|
||||
Transparency: 0.6000000238418579
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.41568630933761597,
|
||||
0,
|
||||
1
|
||||
],
|
||||
Face: "Bottom",
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10025668350",
|
||||
Transparency: 0.6000000238418579
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.41568630933761597,
|
||||
0,
|
||||
1
|
||||
],
|
||||
Face: "Back",
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10025668350",
|
||||
Transparency: 0.6000000238418579
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.40000003576278687,
|
||||
0,
|
||||
1
|
||||
],
|
||||
Face: "Back",
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10023635072",
|
||||
Transparency: 0.6499999761581421
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.40000003576278687,
|
||||
0,
|
||||
1
|
||||
],
|
||||
Face: "Bottom",
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10023635072",
|
||||
Transparency: 0.6499999761581421
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.40000003576278687,
|
||||
0,
|
||||
1
|
||||
],
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10023635072",
|
||||
Transparency: 0.6499999761581421
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
className: "Texture",
|
||||
name: "Texture",
|
||||
properties: {
|
||||
Color3: [
|
||||
0.40000003576278687,
|
||||
0,
|
||||
1
|
||||
],
|
||||
Face: "Left",
|
||||
StudsPerTileU: 200,
|
||||
StudsPerTileV: 200,
|
||||
TextureContent: "rbxassetid://10023635072",
|
||||
Transparency: 0.6499999761581421
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
{
|
||||
attributes: {
|
||||
Size_Curve: {
|
||||
BinaryString: "AAAAAAAAgD8AAAAAyEWiPWTpPj9Q1747AACAPwAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
},
|
||||
Size_End: 60,
|
||||
Size_Envelope: 5,
|
||||
Size_Start: 45
|
||||
},
|
||||
className: "ParticleEmitter",
|
||||
properties: {
|
||||
Color: {
|
||||
ColorSequence: {
|
||||
keypoints: [
|
||||
{
|
||||
color: [
|
||||
0.6666666865348816,
|
||||
0,
|
||||
1
|
||||
],
|
||||
time: 0
|
||||
},
|
||||
{
|
||||
color: [
|
||||
0.6666666865348816,
|
||||
0,
|
||||
1
|
||||
],
|
||||
time: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
Lifetime: {
|
||||
NumberRange: [
|
||||
0.25,
|
||||
0.6000000238418579
|
||||
]
|
||||
},
|
||||
LightEmission: 1,
|
||||
LockedToPart: true,
|
||||
Size: {
|
||||
NumberSequence: {
|
||||
keypoints: [
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0,
|
||||
value: 45
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.001580123440362513,
|
||||
value: 46.08238983154297
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.006548703648149967,
|
||||
value: 47.16343307495117
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.015281829982995987,
|
||||
value: 48.24135971069336
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.028204884380102158,
|
||||
value: 49.313777923583984
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.045797109603881836,
|
||||
value: 50.37750244140625
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.06858612596988678,
|
||||
value: 51.42829513549805
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.09713879972696304,
|
||||
value: 52.4605827331543
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.13203296065330505,
|
||||
value: 53.46717071533203
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.17381449043750763,
|
||||
value: 54.438995361328125
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.22292612493038177,
|
||||
value: 55.36511993408203
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.27961885929107666,
|
||||
value: 56.23305892944336
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.34385624527931213,
|
||||
value: 57.029701232910156
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.4152511656284332,
|
||||
value: 57.74271774291992
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.49307429790496826,
|
||||
value: 58.362186431884766
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.5763297080993652,
|
||||
value: 58.88185501098633
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.6638965010643005,
|
||||
value: 59.29954147338867
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.7546600699424744,
|
||||
value: 59.6165657043457
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.8476089239120483,
|
||||
value: 59.83663558959961
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 1,
|
||||
value: 59.964595794677734
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
Speed: {
|
||||
NumberRange: [
|
||||
0.0010000000474974513,
|
||||
0.0010000000474974513
|
||||
]
|
||||
},
|
||||
Tags: [
|
||||
"_VFX"
|
||||
],
|
||||
Texture: "rbxassetid://77024587778878",
|
||||
Transparency: {
|
||||
NumberSequence: {
|
||||
keypoints: [
|
||||
{
|
||||
envelope: 0,
|
||||
time: 0,
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
envelope: 0,
|
||||
time: 0.09989999979734421,
|
||||
value: 0.75
|
||||
},
|
||||
{
|
||||
envelope: 0,
|
||||
time: 0.6000000238418579,
|
||||
value: 0.75
|
||||
},
|
||||
{
|
||||
envelope: 0,
|
||||
time: 1,
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
{
|
||||
attributes: {
|
||||
Size_Curve: {
|
||||
BinaryString: "AAAAAAAAgD8AAAAAyEWiPWTpPj9Q1747AACAPwAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
},
|
||||
Size_End: 60,
|
||||
Size_Envelope: 5,
|
||||
Size_Start: 45
|
||||
},
|
||||
className: "ParticleEmitter",
|
||||
name: "glow",
|
||||
properties: {
|
||||
Color: {
|
||||
ColorSequence: {
|
||||
keypoints: [
|
||||
{
|
||||
color: [
|
||||
0.364705890417099,
|
||||
0.11372549086809158,
|
||||
1
|
||||
],
|
||||
time: 0
|
||||
},
|
||||
{
|
||||
color: [
|
||||
0.364705890417099,
|
||||
0.11372549086809158,
|
||||
1
|
||||
],
|
||||
time: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
Lifetime: {
|
||||
NumberRange: [
|
||||
0.25,
|
||||
0.6000000238418579
|
||||
]
|
||||
},
|
||||
LightEmission: 1,
|
||||
LockedToPart: true,
|
||||
Size: {
|
||||
NumberSequence: {
|
||||
keypoints: [
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0,
|
||||
value: 45
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.001580123440362513,
|
||||
value: 46.08238983154297
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.006548703648149967,
|
||||
value: 47.16343307495117
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.015281829982995987,
|
||||
value: 48.24135971069336
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.028204884380102158,
|
||||
value: 49.313777923583984
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.045797109603881836,
|
||||
value: 50.37750244140625
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.06858612596988678,
|
||||
value: 51.42829513549805
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.09713879972696304,
|
||||
value: 52.4605827331543
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.13203296065330505,
|
||||
value: 53.46717071533203
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.17381449043750763,
|
||||
value: 54.438995361328125
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.22292612493038177,
|
||||
value: 55.36511993408203
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.27961885929107666,
|
||||
value: 56.23305892944336
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.34385624527931213,
|
||||
value: 57.029701232910156
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.4152511656284332,
|
||||
value: 57.74271774291992
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.49307429790496826,
|
||||
value: 58.362186431884766
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.5763297080993652,
|
||||
value: 58.88185501098633
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.6638965010643005,
|
||||
value: 59.29954147338867
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.7546600699424744,
|
||||
value: 59.6165657043457
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.8476089239120483,
|
||||
value: 59.83663558959961
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 1,
|
||||
value: 59.964595794677734
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
Speed: {
|
||||
NumberRange: [
|
||||
0.0010000000474974513,
|
||||
0.0010000000474974513
|
||||
]
|
||||
},
|
||||
Tags: [
|
||||
"_VFX"
|
||||
],
|
||||
Texture: "rbxassetid://77024587778878",
|
||||
Transparency: {
|
||||
NumberSequence: {
|
||||
keypoints: [
|
||||
{
|
||||
envelope: 0,
|
||||
time: 0,
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
envelope: 0,
|
||||
time: 0.09989999979734421,
|
||||
value: 0.75
|
||||
},
|
||||
{
|
||||
envelope: 0,
|
||||
time: 0.6000000238418579,
|
||||
value: 0.75
|
||||
},
|
||||
{
|
||||
envelope: 0,
|
||||
time: 1,
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
{
|
||||
attributes: {
|
||||
Size_Curve: {
|
||||
BinaryString: "AAAAAAAAgD8AAAAAyEWiPWTpPj9Q1747AACAPwAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
},
|
||||
Size_End: 60,
|
||||
Size_Envelope: 5,
|
||||
Size_Start: 45
|
||||
},
|
||||
className: "ParticleEmitter",
|
||||
name: "glow",
|
||||
properties: {
|
||||
Color: {
|
||||
ColorSequence: {
|
||||
keypoints: [
|
||||
{
|
||||
color: [
|
||||
0.6666666865348816,
|
||||
0,
|
||||
1
|
||||
],
|
||||
time: 0
|
||||
},
|
||||
{
|
||||
color: [
|
||||
0.6666666865348816,
|
||||
0,
|
||||
1
|
||||
],
|
||||
time: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
Lifetime: {
|
||||
NumberRange: [
|
||||
0.25,
|
||||
0.6000000238418579
|
||||
]
|
||||
},
|
||||
LightEmission: 1,
|
||||
LockedToPart: true,
|
||||
Size: {
|
||||
NumberSequence: {
|
||||
keypoints: [
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0,
|
||||
value: 45
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.001580123440362513,
|
||||
value: 46.08238983154297
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.006548703648149967,
|
||||
value: 47.16343307495117
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.015281829982995987,
|
||||
value: 48.24135971069336
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.028204884380102158,
|
||||
value: 49.313777923583984
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.045797109603881836,
|
||||
value: 50.37750244140625
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.06858612596988678,
|
||||
value: 51.42829513549805
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.09713879972696304,
|
||||
value: 52.4605827331543
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.13203296065330505,
|
||||
value: 53.46717071533203
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.17381449043750763,
|
||||
value: 54.438995361328125
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.22292612493038177,
|
||||
value: 55.36511993408203
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.27961885929107666,
|
||||
value: 56.23305892944336
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.34385624527931213,
|
||||
value: 57.029701232910156
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.4152511656284332,
|
||||
value: 57.74271774291992
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.49307429790496826,
|
||||
value: 58.362186431884766
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.5763297080993652,
|
||||
value: 58.88185501098633
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.6638965010643005,
|
||||
value: 59.29954147338867
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.7546600699424744,
|
||||
value: 59.6165657043457
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 0.8476089239120483,
|
||||
value: 59.83663558959961
|
||||
},
|
||||
{
|
||||
envelope: 5,
|
||||
time: 1,
|
||||
value: 59.964595794677734
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
Speed: {
|
||||
NumberRange: [
|
||||
0.0010000000474974513,
|
||||
0.0010000000474974513
|
||||
]
|
||||
},
|
||||
Tags: [
|
||||
"_VFX"
|
||||
],
|
||||
Texture: "rbxassetid://77024587778878",
|
||||
Transparency: {
|
||||
NumberSequence: {
|
||||
keypoints: [
|
||||
{
|
||||
envelope: 0,
|
||||
time: 0,
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
envelope: 0,
|
||||
time: 0.09989999979734421,
|
||||
value: 0.75
|
||||
},
|
||||
{
|
||||
envelope: 0,
|
||||
time: 0.6000000238418579,
|
||||
value: 0.75
|
||||
},
|
||||
{
|
||||
envelope: 0,
|
||||
time: 1,
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
className: "UnionOperation",
|
||||
properties: {
|
||||
Anchored: true,
|
||||
AssetId: "http://www.roblox.com//asset/?id=212226088",
|
||||
CFrame: [
|
||||
801.715576171875,
|
||||
36.375,
|
||||
0.00000762939453125,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
CanCollide: false,
|
||||
CanQuery: false,
|
||||
CanTouch: false,
|
||||
CastShadow: false,
|
||||
Color: {
|
||||
Color3uint8: [
|
||||
48,
|
||||
12,
|
||||
71
|
||||
]
|
||||
},
|
||||
FormFactor: "Symmetric",
|
||||
InitialSize: [
|
||||
18.943885803222656,
|
||||
6.7999587059021,
|
||||
39.99999237060547
|
||||
],
|
||||
Size: [
|
||||
49.43058776855469,
|
||||
72.75,
|
||||
260
|
||||
],
|
||||
UnscaledCofm: [
|
||||
-1.2760223150253296,
|
||||
-0.917913019657135,
|
||||
0.000006928649327164749
|
||||
],
|
||||
UnscaledVolInertiaDiags: [
|
||||
321672.25,
|
||||
343861.1875,
|
||||
37455.859375
|
||||
],
|
||||
UnscaledVolInertiaOffDiags: [
|
||||
5263.43994140625,
|
||||
0.014713685028254986,
|
||||
-0.0022491957060992718
|
||||
],
|
||||
UnscaledVolume: 2355.29296875,
|
||||
UsePartColor: true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
attributes: {
|
||||
Rojo_Ref_PrimaryPart: "ServerStorage/UFOWave/TsunamiWave",
|
||||
Speed: 10,
|
||||
ZonesPassed: {
|
||||
Float32: 0
|
||||
}
|
||||
},
|
||||
className: "Model",
|
||||
properties: {
|
||||
ModelMeshCFrame: [
|
||||
-740.2847900390625,
|
||||
-71.00001525878906,
|
||||
-369.2483825683594,
|
||||
-2.6309862732887268e-8,
|
||||
0,
|
||||
-1.000003695487976,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1.000003695487976,
|
||||
0,
|
||||
-2.6309862732887268e-8
|
||||
],
|
||||
WorldPivotData: {
|
||||
OptionalCFrame: {
|
||||
orientation: [
|
||||
[
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
1
|
||||
]
|
||||
],
|
||||
position: [
|
||||
801.715576171875,
|
||||
36.375,
|
||||
0.00000762939453125
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
rojo-test/syncback-tests/UFOWave_matching/input.rbxm
Normal file
BIN
rojo-test/syncback-tests/UFOWave_matching/input.rbxm
Normal file
Binary file not shown.
|
|
@ -739,6 +739,449 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// Helper: builds a RojoTree from child snapshots (with properties).
|
||||
fn make_tree_from_snapshots(children: Vec<InstanceSnapshot>) -> (RojoTree, Vec<Ref>) {
|
||||
let root_snap = InstanceSnapshot {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: InstanceMetadata::default(),
|
||||
name: Cow::Borrowed("DataModel"),
|
||||
class_name: ustr("DataModel"),
|
||||
properties: Default::default(),
|
||||
children,
|
||||
};
|
||||
let tree = RojoTree::new(root_snap);
|
||||
let root_id = tree.get_root_id();
|
||||
let child_ids: Vec<Ref> = tree.get_instance(root_id).unwrap().children().to_vec();
|
||||
(tree, child_ids)
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Default-omission matching tests
|
||||
//
|
||||
// These reproduce the scenario where syncback strips default-valued
|
||||
// properties from model files, creating an asymmetry between the
|
||||
// snapshot (sparse) and tree (full properties from Studio).
|
||||
//
|
||||
// The matching must still pair instances correctly despite some
|
||||
// snapshots missing properties that the tree has at their defaults.
|
||||
// ================================================================
|
||||
|
||||
#[test]
|
||||
fn reflection_database_has_expected_defaults() {
|
||||
// Validates the reflection database has the defaults we rely on.
|
||||
use rbx_dom_weak::types::Variant;
|
||||
|
||||
let db = rbx_reflection_database::get().expect("reflection database should load");
|
||||
let texture = db.classes.get("Texture").expect("Texture class should exist");
|
||||
let face_default = texture
|
||||
.default_properties
|
||||
.get("Face")
|
||||
.expect("Texture should have Face default");
|
||||
match face_default {
|
||||
Variant::Enum(e) => assert_eq!(e.to_u32(), 5, "Face default should be Front (5)"),
|
||||
other => panic!("Face default should be Enum, got {:?}", other),
|
||||
}
|
||||
|
||||
let part = db.classes.get("Part").expect("Part class should exist");
|
||||
let anchored_default = part
|
||||
.default_properties
|
||||
.get("Anchored")
|
||||
.expect("Part should have Anchored default");
|
||||
assert_eq!(
|
||||
anchored_default,
|
||||
&Variant::Bool(false),
|
||||
"Anchored default should be false"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn texture_face_six_instances_default_omitted() {
|
||||
// Exact reproduction of the Texture Face bug:
|
||||
// 6 Textures all named "Texture", each with a different Face.
|
||||
// Snapshot side: 5 have explicit Face, 1 omits Face (Front=default).
|
||||
// Tree side: all 6 have explicit Face (populated from Studio).
|
||||
//
|
||||
// Without the default-aware fix, the snapshot without Face scores
|
||||
// equally against all tree instances, steals one by iteration order,
|
||||
// and displaces the correct match.
|
||||
use rbx_dom_weak::types::{Enum, Variant};
|
||||
|
||||
let faces = [
|
||||
("Top", Enum::from_u32(1)),
|
||||
("Right", Enum::from_u32(0)),
|
||||
("Left", Enum::from_u32(3)),
|
||||
("Bottom", Enum::from_u32(4)),
|
||||
("Back", Enum::from_u32(2)),
|
||||
];
|
||||
let face_front = Enum::from_u32(5);
|
||||
|
||||
let shared_props = vec![("Transparency", Variant::Float32(0.65))];
|
||||
|
||||
// Snapshots: 5 with explicit Face + 1 without (Front default)
|
||||
let mut snaps: Vec<InstanceSnapshot> = faces
|
||||
.iter()
|
||||
.map(|(_, face_enum)| {
|
||||
let mut props = shared_props.clone();
|
||||
props.push(("Face", Variant::Enum(face_enum.clone())));
|
||||
make_snapshot_with_props("Texture", "Texture", props)
|
||||
})
|
||||
.collect();
|
||||
// The Front one: NO Face property (default omitted by syncback)
|
||||
snaps.push(make_snapshot_with_props(
|
||||
"Texture",
|
||||
"Texture",
|
||||
shared_props.clone(),
|
||||
));
|
||||
|
||||
// Tree: all 6 with explicit Face (Studio always has all properties)
|
||||
let mut tree_children: Vec<InstanceSnapshot> = faces
|
||||
.iter()
|
||||
.map(|(_, face_enum)| {
|
||||
let mut props = shared_props.clone();
|
||||
props.push(("Face", Variant::Enum(face_enum.clone())));
|
||||
make_snapshot_with_props("Texture", "Texture", props)
|
||||
})
|
||||
.collect();
|
||||
tree_children.push(make_snapshot_with_props(
|
||||
"Texture",
|
||||
"Texture",
|
||||
{
|
||||
let mut props = shared_props.clone();
|
||||
props.push(("Face", Variant::Enum(face_front.clone())));
|
||||
props
|
||||
},
|
||||
));
|
||||
|
||||
let (tree, tree_refs) = make_tree_from_snapshots(tree_children);
|
||||
let result = match_forward(snaps, &tree_refs, &tree);
|
||||
|
||||
assert_eq!(result.matched.len(), 6);
|
||||
assert!(result.unmatched_snapshot.is_empty());
|
||||
assert!(result.unmatched_tree.is_empty());
|
||||
|
||||
// Verify each pair has matching Face values.
|
||||
for (snap, tree_ref) in &result.matched {
|
||||
let inst = tree.get_instance(*tree_ref).unwrap();
|
||||
let snap_face = snap.properties.get(&ustr("Face"));
|
||||
let tree_face = inst.properties().get(&ustr("Face")).unwrap();
|
||||
|
||||
match snap_face {
|
||||
Some(sv) => {
|
||||
assert!(
|
||||
variant_eq(sv, tree_face),
|
||||
"Face mismatch: snap={:?}, tree={:?}",
|
||||
sv,
|
||||
tree_face
|
||||
);
|
||||
}
|
||||
None => {
|
||||
// Snapshot omitted Face = must match tree Face=Front (5)
|
||||
assert!(
|
||||
variant_eq(tree_face, &Variant::Enum(face_front.clone())),
|
||||
"Snapshot without Face should match Front, got {:?}",
|
||||
tree_face
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn texture_face_twelve_instances_two_groups() {
|
||||
// Exact reproduction of the user's real scenario:
|
||||
// 12 Textures split into two groups of 6 by TextureContent.
|
||||
// Each group covers all 6 faces, with Front omitted (default).
|
||||
// Tree has all 12 with explicit Face.
|
||||
use rbx_dom_weak::types::{Enum, Variant};
|
||||
|
||||
let face_values: Vec<Enum> = vec![
|
||||
Enum::from_u32(1), // Top
|
||||
Enum::from_u32(0), // Right
|
||||
Enum::from_u32(3), // Left
|
||||
Enum::from_u32(4), // Bottom
|
||||
Enum::from_u32(2), // Back
|
||||
];
|
||||
let face_front = Enum::from_u32(5);
|
||||
|
||||
let mut snaps = Vec::new();
|
||||
let mut tree_children = Vec::new();
|
||||
|
||||
for group_transparency in [0.6_f32, 0.65_f32] {
|
||||
// 5 with explicit Face
|
||||
for face in &face_values {
|
||||
snaps.push(make_snapshot_with_props(
|
||||
"Texture",
|
||||
"Texture",
|
||||
vec![
|
||||
("Transparency", Variant::Float32(group_transparency)),
|
||||
("Face", Variant::Enum(face.clone())),
|
||||
],
|
||||
));
|
||||
tree_children.push(make_snapshot_with_props(
|
||||
"Texture",
|
||||
"Texture",
|
||||
vec![
|
||||
("Transparency", Variant::Float32(group_transparency)),
|
||||
("Face", Variant::Enum(face.clone())),
|
||||
],
|
||||
));
|
||||
}
|
||||
// 1 without Face (Front default)
|
||||
snaps.push(make_snapshot_with_props(
|
||||
"Texture",
|
||||
"Texture",
|
||||
vec![("Transparency", Variant::Float32(group_transparency))],
|
||||
));
|
||||
tree_children.push(make_snapshot_with_props(
|
||||
"Texture",
|
||||
"Texture",
|
||||
vec![
|
||||
("Transparency", Variant::Float32(group_transparency)),
|
||||
("Face", Variant::Enum(face_front.clone())),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
// Reverse tree order to make it harder
|
||||
tree_children.reverse();
|
||||
|
||||
let (tree, tree_refs) = make_tree_from_snapshots(tree_children);
|
||||
let result = match_forward(snaps, &tree_refs, &tree);
|
||||
|
||||
assert_eq!(result.matched.len(), 12);
|
||||
assert!(result.unmatched_snapshot.is_empty());
|
||||
assert!(result.unmatched_tree.is_empty());
|
||||
|
||||
for (snap, tree_ref) in &result.matched {
|
||||
let inst = tree.get_instance(*tree_ref).unwrap();
|
||||
let snap_face = snap.properties.get(&ustr("Face"));
|
||||
let tree_face = inst.properties().get(&ustr("Face")).unwrap();
|
||||
|
||||
// Transparency must match (distinguishes the two groups)
|
||||
let snap_t = snap.properties.get(&ustr("Transparency")).unwrap();
|
||||
let tree_t = inst.properties().get(&ustr("Transparency")).unwrap();
|
||||
assert!(
|
||||
variant_eq(snap_t, tree_t),
|
||||
"Group mismatch: snap Transparency={:?}, tree={:?}",
|
||||
snap_t,
|
||||
tree_t
|
||||
);
|
||||
|
||||
match snap_face {
|
||||
Some(sv) => assert!(
|
||||
variant_eq(sv, tree_face),
|
||||
"Face mismatch: snap={:?}, tree={:?}",
|
||||
sv,
|
||||
tree_face
|
||||
),
|
||||
None => assert!(
|
||||
variant_eq(tree_face, &Variant::Enum(face_front.clone())),
|
||||
"Omitted Face should match Front, got {:?}",
|
||||
tree_face
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part_anchored_default_omitted() {
|
||||
// 4 Parts named "Block": 3 have Anchored=true (explicit),
|
||||
// 1 omits Anchored (false is the default). Tree has all 4.
|
||||
use rbx_dom_weak::types::Variant;
|
||||
|
||||
let mut snaps = Vec::new();
|
||||
let mut tree_children = Vec::new();
|
||||
|
||||
for i in 0..3 {
|
||||
let props = vec![
|
||||
("Transparency", Variant::Float32(i as f32 * 0.1)),
|
||||
("Anchored", Variant::Bool(true)),
|
||||
];
|
||||
snaps.push(make_snapshot_with_props("Block", "Part", props.clone()));
|
||||
tree_children.push(make_snapshot_with_props("Block", "Part", props));
|
||||
}
|
||||
// The one with default Anchored=false: snapshot omits it
|
||||
snaps.push(make_snapshot_with_props(
|
||||
"Block",
|
||||
"Part",
|
||||
vec![("Transparency", Variant::Float32(0.3))],
|
||||
));
|
||||
tree_children.push(make_snapshot_with_props(
|
||||
"Block",
|
||||
"Part",
|
||||
vec![
|
||||
("Transparency", Variant::Float32(0.3)),
|
||||
("Anchored", Variant::Bool(false)),
|
||||
],
|
||||
));
|
||||
|
||||
tree_children.reverse();
|
||||
|
||||
let (tree, tree_refs) = make_tree_from_snapshots(tree_children);
|
||||
let result = match_forward(snaps, &tree_refs, &tree);
|
||||
|
||||
assert_eq!(result.matched.len(), 4);
|
||||
|
||||
for (snap, tree_ref) in &result.matched {
|
||||
let inst = tree.get_instance(*tree_ref).unwrap();
|
||||
let snap_t = snap.properties.get(&ustr("Transparency")).unwrap();
|
||||
let tree_t = inst.properties().get(&ustr("Transparency")).unwrap();
|
||||
assert!(
|
||||
variant_eq(snap_t, tree_t),
|
||||
"Transparency mismatch: snap={:?}, tree={:?}",
|
||||
snap_t,
|
||||
tree_t
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_defaults_omitted_all_at_once() {
|
||||
// Tree instances have MANY extra properties all at their class defaults.
|
||||
// Snapshots omit all of them. The match should not be thrown off.
|
||||
use rbx_dom_weak::types::Variant;
|
||||
|
||||
// Two sparse snapshots differing only by Transparency
|
||||
let snap_a = make_snapshot_with_props(
|
||||
"Wall",
|
||||
"Part",
|
||||
vec![("Transparency", Variant::Float32(0.0))],
|
||||
);
|
||||
let snap_b = make_snapshot_with_props(
|
||||
"Wall",
|
||||
"Part",
|
||||
vec![("Transparency", Variant::Float32(1.0))],
|
||||
);
|
||||
|
||||
// Tree has Transparency + many known Part defaults from Studio.
|
||||
// These are all at their default values, so count_own_diffs should
|
||||
// skip them (not inflate match cost).
|
||||
let extra_defaults: Vec<(&str, Variant)> = vec![
|
||||
("Anchored", Variant::Bool(false)),
|
||||
("CastShadow", Variant::Bool(true)),
|
||||
("CanCollide", Variant::Bool(true)),
|
||||
("CanTouch", Variant::Bool(true)),
|
||||
("Locked", Variant::Bool(false)),
|
||||
("Massless", Variant::Bool(false)),
|
||||
];
|
||||
|
||||
let mut tree_props_a = vec![("Transparency", Variant::Float32(0.0))];
|
||||
tree_props_a.extend(extra_defaults.iter().cloned());
|
||||
let mut tree_props_b = vec![("Transparency", Variant::Float32(1.0))];
|
||||
tree_props_b.extend(extra_defaults.iter().cloned());
|
||||
|
||||
// Tree in reversed order
|
||||
let (tree, tree_refs) = make_tree_from_snapshots(vec![
|
||||
make_snapshot_with_props("Wall", "Part", tree_props_b),
|
||||
make_snapshot_with_props("Wall", "Part", tree_props_a),
|
||||
]);
|
||||
let result = match_forward(vec![snap_a, snap_b], &tree_refs, &tree);
|
||||
|
||||
assert_eq!(result.matched.len(), 2, "Both should match");
|
||||
|
||||
for (snap, tree_ref) in &result.matched {
|
||||
let inst = tree.get_instance(*tree_ref).unwrap();
|
||||
let snap_t = snap.properties.get(&ustr("Transparency")).unwrap();
|
||||
let tree_t = inst.properties().get(&ustr("Transparency")).unwrap();
|
||||
assert!(
|
||||
variant_eq(snap_t, tree_t),
|
||||
"Part with 6 extra default props mismatched: snap={:?}, tree={:?}",
|
||||
snap_t,
|
||||
tree_t
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ten_ambiguous_textures_stress() {
|
||||
// 10 Textures: 8 with unique Face values (some repeated across
|
||||
// groups), 2 with Front omitted. Two distinguishing groups by
|
||||
// Transparency. The matcher must handle this without mis-pairing.
|
||||
use rbx_dom_weak::types::{Enum, Variant};
|
||||
|
||||
let faces_group_a = [
|
||||
Some(Enum::from_u32(1)), // Top
|
||||
Some(Enum::from_u32(0)), // Right
|
||||
Some(Enum::from_u32(3)), // Left
|
||||
None, // Front (default, omitted)
|
||||
Some(Enum::from_u32(4)), // Bottom
|
||||
];
|
||||
let faces_group_b = [
|
||||
Some(Enum::from_u32(2)), // Back
|
||||
Some(Enum::from_u32(4)), // Bottom
|
||||
None, // Front (default, omitted)
|
||||
Some(Enum::from_u32(1)), // Top
|
||||
Some(Enum::from_u32(3)), // Left
|
||||
];
|
||||
let face_front = Enum::from_u32(5);
|
||||
|
||||
let mut snaps = Vec::new();
|
||||
let mut tree_children = Vec::new();
|
||||
|
||||
for (group_t, faces) in [(0.5_f32, &faces_group_a[..]), (0.8_f32, &faces_group_b[..])] {
|
||||
for face_opt in faces {
|
||||
let mut snap_props = vec![("Transparency", Variant::Float32(group_t))];
|
||||
let mut tree_props = vec![("Transparency", Variant::Float32(group_t))];
|
||||
|
||||
if let Some(face) = face_opt {
|
||||
snap_props.push(("Face", Variant::Enum(face.clone())));
|
||||
tree_props.push(("Face", Variant::Enum(face.clone())));
|
||||
} else {
|
||||
// Snapshot omits Face; tree has Front
|
||||
tree_props.push(("Face", Variant::Enum(face_front.clone())));
|
||||
}
|
||||
|
||||
snaps.push(make_snapshot_with_props("Texture", "Texture", snap_props));
|
||||
tree_children.push(make_snapshot_with_props(
|
||||
"Texture",
|
||||
"Texture",
|
||||
tree_props,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle tree order (reverse)
|
||||
tree_children.reverse();
|
||||
|
||||
let (tree, tree_refs) = make_tree_from_snapshots(tree_children);
|
||||
let result = match_forward(snaps, &tree_refs, &tree);
|
||||
|
||||
assert_eq!(result.matched.len(), 10);
|
||||
assert!(result.unmatched_snapshot.is_empty());
|
||||
assert!(result.unmatched_tree.is_empty());
|
||||
|
||||
for (snap, tree_ref) in &result.matched {
|
||||
let inst = tree.get_instance(*tree_ref).unwrap();
|
||||
|
||||
// Transparency must match (group discriminator)
|
||||
let snap_t = snap.properties.get(&ustr("Transparency")).unwrap();
|
||||
let tree_t = inst.properties().get(&ustr("Transparency")).unwrap();
|
||||
assert!(
|
||||
variant_eq(snap_t, tree_t),
|
||||
"Group mismatch: snap={:?}, tree={:?}",
|
||||
snap_t,
|
||||
tree_t
|
||||
);
|
||||
|
||||
// Face must match
|
||||
let snap_face = snap.properties.get(&ustr("Face"));
|
||||
let tree_face = inst.properties().get(&ustr("Face")).unwrap();
|
||||
match snap_face {
|
||||
Some(sv) => assert!(
|
||||
variant_eq(sv, tree_face),
|
||||
"Face mismatch: snap={:?}, tree={:?}",
|
||||
sv,
|
||||
tree_face
|
||||
),
|
||||
None => assert!(
|
||||
variant_eq(tree_face, &Variant::Enum(face_front.clone())),
|
||||
"Omitted Face should match Front, got {:?}",
|
||||
tree_face
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depth_limit_completes_quickly() {
|
||||
// Deeply nested tree (depth=6) with same-named instances at each level.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue