mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 22:11:38 +00:00
- Introduced a new function `getClassComparisonKeys` in `rbx_dom_lua/init.lua` to pre-compute and cache comparison keys for classes, improving efficiency in property matching. - Updated the `Matching` module in `Reconciler/matching.lua` to utilize pre-computed caches for virtual and Studio instances, optimizing the reconciliation process. - Enhanced handling of excluded properties and types during comparisons, ensuring accurate matching while reducing unnecessary computations. These changes significantly improve the performance and accuracy of property reconciliation, addressing previous inefficiencies in the matching logic.
253 lines
7.1 KiB
Lua
253 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,
|
|
}
|
|
|
|
-- 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,
|
|
}
|