rbx-dom
rbx-dom copied to clipboard
rbx_dom_weak should have a concept of "proxy properties"
Proxy properties are those properties that are mutually dependent, for example (I'll only be listing read and/or write scriptable properties here, in order of how important I think they are):
-
BasePart.Position
,BasePart.Orientation
,BasePart.Rotation
, andBasePart.CFrame
-
Attachment.CFrame
,Attachment.Orientation
,Attachment.Position
,Attachment.Rotation
,Attachment.WorldCFrame
etc. -
Lighting.TimeOfDay
andLighting.ClockTime
-
Model.WorldPivot
,Model.PrimaryPart
, andBasePart.PivotOffset
-
BasePart.BrickColor
andBasePart.Color
-
BaseScript.Enabled
andBaseScript.Disabled
-
ScreenGui.IgnoreGuiInset
andScreenGui.ScreenInsets
- Others?
In Roblox's DOM, proxy properties are populated and reflect changes to one another out of the box. It's a questionable amount of work to catalogue and maintain the consistency of proxy properties in a runtime like Lune, and right now, consumers are totally responsible for both.
At the same time, this will mean more maintenance burden on us. It would be great to find some data we could use to automate some of this (in my cursory search of Max's client tracker I was unable to find any), but failing that we could target only the most important proxy properties.
Given that we currently expose Instance.properties
as a public struct member, it isn't possible to keep track of this 100%. However, I think if we added something akin to Instance.set_property
and Instance.get_property
it wouldn't be unreasonable to have 'proxies' that could intercept what a property was being read or written as.
However, I think that might be beyond rbx_dom_weak
. It is, by its nature weakly typed so it knowing about properties like this feels weird. Between things like UniqueId and this, it may be time to start seriously considering what an implementation of rbx_dom_strong
would look like.
rbx_dom_strong
I'm still not convinced that this would be useful, but this is a conversation for another issue
So anyway ... I think we can at least automate cataloguing proxy properties in rbx_reflector by using the injected plugin to set properties on each instance in a defaults place while listening for changes to any other properties that happen as a result. This will give us information about which proxy properties are linked to each other (for every property settable from Lua, or at least most of them) which we can include in the reflection database.
This does not solve the problem of how to convert to and from proxy properties - at this point that still has to be manual, but we should be able to get a complete picture of what proxy properties exist
I'm still not convinced that this would be useful, but this is a conversation for another issue
I think that's basically what we're headed for anyway, since we're finding a need to be aware of properties outside of the reflection database. I'm not saying it need to be a strongly typed DOM (I don't even know what that would look like) but we should seriously consider what the future looks like if we need to be aware of invariants and proxies inside rbx_dom_weak
itself. If nothing else, there will need to be a layer on top of Instance
s that is aware of proxies for this to work automatically.
I think your proposal for cataloging them is fine, though brute-forcing properties feels gross. I tested it with Sound
and it does correctly catch the proxied properties, at least those that are scriptable.
So anyway ... I think we can at least automate cataloguing proxy properties in rbx_reflector by using the injected plugin to set properties on each instance in a defaults place while listening for changes to any other properties that happen as a result. This will give us information about which proxy properties are linked to each other (for every property settable from Lua, or at least most of them) which we can include in the reflection database.
Adding onto this, this should get your almost a complete picture as it will expose NotScriptable proxies too. For example
local Part = Instance.new("Part")
Part.Changed:Connect(warn) --> BrickColor, Color, Color3uint8
Part.Color = Color3.new()
Also, here's a decent list (though probably incomplete) of NotScriptable proxies and their scriptable counterparts. Do note that the list only consists of properties with CanLoad & CanSave being true.
NotScriptableProxies = {
Players = { MaxPlayersInternal = "MaxPlayers", PreferredPlayersInternal = "PreferredPlayers" },
-- DebuggerBreakpoint = {line="Line"}, -- ? This shouldn't appear in live games (try to prove this wrong)
BallSocketConstraint = { MaxFrictionTorqueXml = "MaxFrictionTorque" },
BasePart = {
Color3uint8 = "Color",
MaterialVariantSerialized = "MaterialVariant",
size = "Size",
_Inheritors = {
TriangleMeshPart = {
FluidFidelityInternal = "FluidFidelity",
_Inheritors = {
MeshPart = { InitialSize = "MeshSize" },
PartOperation = { InitialSize = "MeshSize" },
},
},
FormFactorPart = { formFactorRaw = "FormFactor", _Inheritors = { Part = { shape = "Shape" } } },
TrussPart = { style = "Style" },
},
},
-- CustomEvent = {PersistedCurrentValue=function(instance) -- * Class is Deprecated and :SetValue doesn't seem to affect GetCurrentValue anymore
-- local Receiver = instance:GetAttachedReceivers()[1]
-- if Receiver then
-- return Receiver:GetCurrentValue()
-- else
-- error("No Receiver")
-- end
-- end},
DoubleConstrainedValue = { value = "Value" },
IntConstrainedValue = { value = "Value" },
Fire = { heat_xml = "Heat", size_xml = "Size" },
Humanoid = { Health_XML = "Health" },
MaterialService = { Use2022MaterialsXml = "Use2022Materials" },
Model = {
ScaleFactor = function(instance)
return instance:GetScale()
end,
WorldPivotData = "WorldPivot",
-- ModelMeshCFrame = "Pivot Offset", -- * Both are NotScriptable
-- _Inheritors = {
-- Workspace = { SignalBehavior2 = "SignalBehavior" }, -- * Both are NotScriptable
-- },
},
PackageLink = { PackageIdSerialize = "PackageId", VersionIdSerialize = "VersionNumber" },
StarterPlayer = { AvatarJointUpgrade_Serialized = "AvatarJointUpgrade" },
Smoke = { size_xml = "Size", opacity_xml = "Opacity", riseVelocity_xml = "RiseVelocity" },
Sound = {
xmlRead_MaxDistance_3 = "RollOffMaxDistance", -- * Also MaxDistance
},
-- ViewportFrame = { -- * Pointless because these reflect CurrentCamera's properties
-- CameraCFrame = function(instance) -- *
-- local CurrentCamera = instance.CurrentCamera
-- if CurrentCamera then
-- return CurrentCamera.CFrame
-- else
-- error("No CurrentCamera")
-- end
-- end,
-- -- CameraFieldOfView =
-- },
WeldConstraint = {
Part0Internal = "Part0",
Part1Internal = "Part1",
-- State = function(instance)
-- -- If untouched then default state is 3 (default true)
-- return instance.Enabled and 1 or 0
-- end,
},
}
Hopefully this helps! And, let me know if you discover any more.