mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
- Added 'Name' to the list of excluded property names in the rbx_dom_lua module to prevent unnecessary comparisons. - Updated color handling in the diff stress tests to use Color3.fromRGB for better precision and clarity in color representation. - Improved the hydrate function to initialize a new session if none is provided, enhancing session management during instance hydration. - Refined the trueEquals function to handle NaN comparisons correctly, ensuring accurate equality checks for numeric values. These changes improve the accuracy of property comparisons and enhance the overall functionality of the Reconciler module.
254 lines
7.1 KiB
Lua
254 lines
7.1 KiB
Lua
local database = require(script.database)
|
|
local Error = require(script.Error)
|
|
local PropertyDescriptor = require(script.PropertyDescriptor)
|
|
local EncodedValue = require(script.EncodedValue)
|
|
|
|
-- 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
|
|
|
|
-- Property names excluded from comparison (handled separately).
|
|
local EXCLUDED_PROP_NAMES: { [string]: boolean } = {
|
|
Tags = true,
|
|
Attributes = true,
|
|
Name = true,
|
|
}
|
|
|
|
-- Encoded value type keys that cannot be compared during matching.
|
|
local EXCLUDED_ENCODED_TYPES: { [string]: boolean } = {
|
|
Ref = true,
|
|
UniqueId = true,
|
|
SharedString = true,
|
|
Region3 = true,
|
|
}
|
|
|
|
-- Scriptabilities that allow reading from Studio instances.
|
|
local READABLE_SCRIPTABILITY: { [string]: boolean } = {
|
|
ReadWrite = true,
|
|
Read = true,
|
|
}
|
|
|
|
-- Cache for class comparison keys (propNames + decoded defaults).
|
|
-- Computed once per ClassName, lives for the plugin's lifetime.
|
|
local classComparisonKeysCache: { [string]: any } = {}
|
|
|
|
-- Returns a pre-computed comparison key set for a class:
|
|
-- propNames: ordered array of comparable property names
|
|
-- propNameSet: O(1) lookup set of the same names
|
|
-- defaults: map of propName → decoded native default value
|
|
--
|
|
-- Filtered to: canonical, scriptable (readable), serializable, and
|
|
-- decodable. Excludes Tags, Attributes, Ref, UniqueId, SharedString.
|
|
-- All defaults are pre-decoded to native Roblox values.
|
|
local function getClassComparisonKeys(className: string)
|
|
local cached = classComparisonKeysCache[className]
|
|
if cached then
|
|
return cached
|
|
end
|
|
|
|
local propNames: { string } = {}
|
|
local propNameSet: { [string]: boolean } = {}
|
|
local seenProps: { [string]: boolean } = {}
|
|
|
|
-- Walk the class hierarchy for property metadata
|
|
local currentClassName: string? = className
|
|
repeat
|
|
local currentClass = database.Classes[currentClassName]
|
|
if currentClass == nil then
|
|
break
|
|
end
|
|
|
|
for propertyName, propertyData in currentClass.Properties do
|
|
if seenProps[propertyName] then
|
|
continue
|
|
end
|
|
seenProps[propertyName] = true
|
|
|
|
if EXCLUDED_PROP_NAMES[propertyName] then
|
|
continue
|
|
end
|
|
if not propertyData.Kind or not propertyData.Kind.Canonical then
|
|
continue
|
|
end
|
|
if not READABLE_SCRIPTABILITY[propertyData.Scriptability] then
|
|
continue
|
|
end
|
|
if propertyData.Kind.Canonical.Serialization == "DoesNotSerialize" then
|
|
continue
|
|
end
|
|
|
|
table.insert(propNames, propertyName)
|
|
propNameSet[propertyName] = true
|
|
end
|
|
|
|
currentClassName = currentClass.Superclass
|
|
until currentClassName == nil
|
|
|
|
-- Pre-decode defaults for every comparable property
|
|
local defaults: { [string]: any } = {}
|
|
local encodedDefaults = findDefaultProperties(className)
|
|
for _, propName in propNames do
|
|
local encoded = encodedDefaults[propName]
|
|
if encoded then
|
|
local ty = next(encoded)
|
|
if not EXCLUDED_ENCODED_TYPES[ty] then
|
|
local pcallOk, decodeOk, decoded = pcall(EncodedValue.decode, encoded)
|
|
if pcallOk and decodeOk and decoded ~= nil then
|
|
defaults[propName] = decoded
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local result = table.freeze({
|
|
propNames = table.freeze(propNames),
|
|
propNameSet = table.freeze(propNameSet),
|
|
defaults = table.freeze(defaults),
|
|
})
|
|
classComparisonKeysCache[className] = result
|
|
return result
|
|
end
|
|
|
|
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
|
|
|
|
repeat
|
|
local currentClass = database.Classes[currentClassName]
|
|
|
|
if currentClass == nil then
|
|
return currentClass
|
|
end
|
|
|
|
local propertyData = currentClass.Properties[propertyName]
|
|
if propertyData ~= nil then
|
|
local canonicalData = propertyData.Kind.Canonical
|
|
if canonicalData ~= nil then
|
|
return PropertyDescriptor.fromRaw(propertyData, currentClassName, propertyName)
|
|
end
|
|
|
|
local aliasData = propertyData.Kind.Alias
|
|
if aliasData ~= nil then
|
|
return PropertyDescriptor.fromRaw(
|
|
currentClass.Properties[aliasData.AliasFor],
|
|
currentClassName,
|
|
aliasData.AliasFor
|
|
)
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
currentClassName = currentClass.Superclass
|
|
until currentClassName == nil
|
|
|
|
return nil
|
|
end
|
|
|
|
local function readProperty(instance, propertyName)
|
|
local descriptor = findCanonicalPropertyDescriptor(instance.ClassName, propertyName)
|
|
|
|
if descriptor == nil then
|
|
local fullName = ("%s.%s"):format(instance.className, propertyName)
|
|
|
|
return false, Error.new(Error.Kind.UnknownProperty, fullName)
|
|
end
|
|
|
|
return descriptor:read(instance)
|
|
end
|
|
|
|
local function writeProperty(instance, propertyName, value)
|
|
local descriptor = findCanonicalPropertyDescriptor(instance.ClassName, propertyName)
|
|
|
|
if descriptor == nil then
|
|
local fullName = ("%s.%s"):format(instance.className, propertyName)
|
|
|
|
return false, Error.new(Error.Kind.UnknownProperty, fullName)
|
|
end
|
|
|
|
return descriptor:write(instance, value)
|
|
end
|
|
|
|
return {
|
|
readProperty = readProperty,
|
|
writeProperty = writeProperty,
|
|
findCanonicalPropertyDescriptor = findCanonicalPropertyDescriptor,
|
|
findClassDescriptor = findClassDescriptor,
|
|
findDefaultProperties = findDefaultProperties,
|
|
getClassComparisonKeys = getClassComparisonKeys,
|
|
Error = Error,
|
|
EncodedValue = EncodedValue,
|
|
}
|