Enhance property encoding and class descriptor retrieval in rbx_dom_lua

- Introduced a new function, `findClassDescriptor`, to retrieve class properties and metadata, including inherited properties, from the database.
- Updated `encodeInstance` to include encoding for Tags alongside Attributes, ensuring comprehensive property serialization.
- Improved property filtering logic to skip non-serializable properties and those matching default values, aligning with syncback behavior.
- Enhanced comments for clarity on property handling and encoding processes.
This commit is contained in:
Joe 2026-01-30 13:18:20 -06:00
parent c0d089f0aa
commit dccd3a691a
6 changed files with 188 additions and 62 deletions

View file

@ -2,6 +2,51 @@ local database = require(script.database)
local Error = require(script.Error)
local PropertyDescriptor = require(script.PropertyDescriptor)
-- Returns a class descriptor with all property names and metadata (including inherited ones)
-- 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
local function findClassDescriptor(className)
local classData = database.Classes[className]
if classData == nil then
return nil
end
-- Build a table of all properties, walking up the inheritance chain
local allProperties = {}
local currentClassName = className
repeat
local currentClass = database.Classes[currentClassName]
if currentClass == nil then
break
end
-- Add properties from this class (don't override if already set by subclass)
for propertyName, propertyData in pairs(currentClass.Properties) do
if allProperties[propertyName] == nil then
-- Only include canonical properties (not aliases)
if propertyData.Kind and propertyData.Kind.Canonical then
allProperties[propertyName] = {
name = propertyName,
scriptability = propertyData.Scriptability,
dataType = propertyData.DataType,
serialization = propertyData.Kind.Canonical.Serialization,
}
end
end
end
currentClassName = currentClass.Superclass
until currentClassName == nil
return {
name = classData.Name,
superclass = classData.Superclass,
properties = allProperties,
}
end
local function findCanonicalPropertyDescriptor(className, propertyName)
local currentClassName = className
@ -65,6 +110,7 @@ return {
readProperty = readProperty,
writeProperty = writeProperty,
findCanonicalPropertyDescriptor = findCanonicalPropertyDescriptor,
findClassDescriptor = findClassDescriptor,
Error = Error,
EncodedValue = require(script.EncodedValue),
}

View file

@ -29,22 +29,35 @@ local SCRIPT_CLASS_NAMES = {
ModuleScript = true,
}
-- Properties to skip when encoding (internal Roblox properties)
-- Properties to always skip when encoding (internal Roblox properties that never serialize)
local SKIP_PROPERTIES = {
Parent = true,
Name = true, -- We handle this separately
Archivable = true,
}
-- Classes that become directories but may have properties (like Attributes)
local DIRECTORY_CLASSES = {
Folder = true,
Configuration = true,
Tool = true,
ScreenGui = true,
SurfaceGui = true,
BillboardGui = true,
AdGui = true,
-- Properties to skip per class (matches filter_out_property in Rust syncback)
-- These are properties whose values are encoded in the file itself, not metadata
local SKIP_PROPERTIES_BY_CLASS = {
Script = {
Source = true, -- Encoded in the .luau file
ScriptGuid = true, -- Internal
RunContext = true, -- Encoded in file suffix (.server.luau, .client.luau, etc.)
},
LocalScript = {
Source = true, -- Encoded in the .luau file
ScriptGuid = true, -- Internal
},
ModuleScript = {
Source = true, -- Encoded in the .luau file
ScriptGuid = true, -- Internal
},
LocalizationTable = {
Contents = true, -- Encoded in the .csv file
},
StringValue = {
Value = true, -- Encoded in the .txt file
},
}
-- Finds duplicate names among a list of instances
@ -126,6 +139,25 @@ local function encodeAttributes(instance, properties)
end
end
-- Encode Tags if present on any instance
local function encodeTags(instance, properties)
-- Try to get Tags - this works for all instance types
local success, tags = pcall(function()
return instance:GetTags()
end)
if success and tags and #tags > 0 then
-- Tags need to be encoded specially
local tagDescriptor = RbxDom.findCanonicalPropertyDescriptor(instance.ClassName, "Tags")
if tagDescriptor then
local encodeSuccess, encodeResult = encodeProperty(instance, "Tags", tagDescriptor)
if encodeSuccess and encodeResult ~= nil then
properties.Tags = encodeResult
end
end
end
end
encodeInstance = function(instance, parentId, _skipPathCheck)
-- Check if the entire path to this instance is unique (unless we're in a recursive call)
-- For top-level instances, we check the entire ancestor chain for duplicates
@ -143,11 +175,12 @@ encodeInstance = function(instance, parentId, _skipPathCheck)
local properties = {}
-- Always try to encode Attributes for any instance type
-- Always try to encode Attributes and Tags for any instance type
encodeAttributes(instance, properties)
encodeTags(instance, properties)
-- For scripts, encode the Source property first (required for script files)
if SCRIPT_CLASS_NAMES[instance.ClassName] then
-- For scripts, encode the Source property (required)
local sourceDescriptor = RbxDom.findCanonicalPropertyDescriptor(instance.ClassName, "Source")
if sourceDescriptor then
local encodeSuccess, encodeResult = encodeProperty(instance, "Source", sourceDescriptor)
@ -161,46 +194,42 @@ encodeInstance = function(instance, parentId, _skipPathCheck)
Log.warn("Could not find Source property descriptor for {:?}", instance)
return nil
end
end
-- Also encode other script properties (Disabled, LinkedSource, etc.)
-- These go into the .meta.json5 file
local classDescriptor = RbxDom.findClassDescriptor(instance.ClassName)
if classDescriptor then
for propertyName, propertyDescriptor in classDescriptor.properties do
-- Skip properties we handle separately or should ignore
if SKIP_PROPERTIES[propertyName] then
continue
end
if propertyName == "Source" or propertyName == "Attributes" then
continue
end
-- Encode ALL properties for ANY instance type (matching Rust syncback behavior)
-- The server handles filtering defaults and serialization
local classDescriptor = RbxDom.findClassDescriptor(instance.ClassName)
if classDescriptor then
-- Get class-specific properties to skip
local classSkipProps = SKIP_PROPERTIES_BY_CLASS[instance.ClassName] or {}
-- Only encode serializable properties
if propertyDescriptor.scriptability == "ReadWrite" or propertyDescriptor.scriptability == "Read" then
local encodeSuccess, encodeResult = encodeProperty(instance, propertyName, propertyDescriptor)
if encodeSuccess and encodeResult ~= nil then
properties[propertyName] = encodeResult
end
end
for propertyName, propertyMeta in pairs(classDescriptor.properties) do
-- Skip universally skipped properties
if SKIP_PROPERTIES[propertyName] then
continue
end
end
elseif not DIRECTORY_CLASSES[instance.ClassName] then
-- For non-directory, non-script classes, encode all relevant properties
local classDescriptor = RbxDom.findClassDescriptor(instance.ClassName)
if classDescriptor then
for propertyName, propertyDescriptor in classDescriptor.properties do
if SKIP_PROPERTIES[propertyName] then
continue
end
-- Skip Attributes here since we handle it separately above
if propertyName == "Attributes" then
continue
end
-- Skip class-specific properties (Source, RunContext for scripts, etc.)
if classSkipProps[propertyName] then
continue
end
-- Only encode serializable properties
if propertyDescriptor.scriptability == "ReadWrite" or propertyDescriptor.scriptability == "Read" then
local encodeSuccess, encodeResult = encodeProperty(instance, propertyName, propertyDescriptor)
-- Skip Attributes and Tags since we handle them separately above
if propertyName == "Attributes" or propertyName == "Tags" then
continue
end
-- Only encode properties that:
-- 1. Are readable (ReadWrite or Read scriptability)
-- 2. Actually serialize (not "DoesNotSerialize")
local isReadable = propertyMeta.scriptability == "ReadWrite" or propertyMeta.scriptability == "Read"
local doesSerialize = propertyMeta.serialization ~= "DoesNotSerialize"
if isReadable and doesSerialize then
-- Get the full PropertyDescriptor for encoding
local descriptor = RbxDom.findCanonicalPropertyDescriptor(instance.ClassName, propertyName)
if descriptor then
local encodeSuccess, encodeResult = encodeProperty(instance, propertyName, descriptor)
if encodeSuccess and encodeResult ~= nil then
properties[propertyName] = encodeResult
end
@ -208,7 +237,6 @@ encodeInstance = function(instance, parentId, _skipPathCheck)
end
end
end
-- For directory classes (Folder, Configuration, etc.), we only encode Attributes (done above)
-- Recursively encode children, skipping those with duplicate names
local children = {}