RunContext refactor + sync updates

This commit is contained in:
Joe 2026-01-30 08:12:04 -06:00
parent c7cddb72c2
commit d3cc11659a
79 changed files with 1415 additions and 626 deletions

View file

@ -8,6 +8,12 @@
- All other instances -> .model.json files or directories
Children are recursively encoded and included.
DUPLICATE HANDLING:
- Instances with duplicate-named siblings at ANY level of the path are skipped
- This matches the behavior in ref_properties.rs::is_path_unique
- A path is ambiguous if duplicates exist anywhere from the instance to the root
- A warning is logged when duplicates are encountered
]]
local Packages = script.Parent.Parent.Parent.Packages
@ -41,9 +47,65 @@ local DIRECTORY_CLASSES = {
AdGui = true,
}
-- Finds duplicate names among a list of instances
-- Returns a set of names that appear more than once
local function findDuplicateNames(children: { Instance }): { [string]: boolean }
local nameCounts: { [string]: number } = {}
local duplicates: { [string]: boolean } = {}
for _, child in children do
local name = child.Name
nameCounts[name] = (nameCounts[name] or 0) + 1
if nameCounts[name] > 1 then
duplicates[name] = true
end
end
return duplicates
end
-- Checks if an instance has duplicate-named siblings
local function hasDuplicateSiblings(instance: Instance): boolean
local parent = instance.Parent
if not parent then
return false
end
local siblings = parent:GetChildren()
local duplicates = findDuplicateNames(siblings)
return duplicates[instance.Name] == true
end
-- Checks if the path to an instance is unique by walking up the tree.
-- Returns false if ANY ancestor (including the instance itself) has duplicate siblings.
-- This matches the behavior of is_path_unique in ref_properties.rs
local function isPathUnique(instance: Instance): boolean
local current = instance
while current do
-- Check if current has duplicate-named siblings
if hasDuplicateSiblings(current) then
return false
end
-- Move up to parent
current = current.Parent
-- Stop at game (DataModel) - we've reached the root
if current and current == game then
break
end
end
return true
end
-- Forward declaration for recursion
local encodeInstance
-- Track skipped duplicates for logging
local skippedDuplicateCount = 0
-- Encode Attributes if present on any instance
local function encodeAttributes(instance, properties)
-- Try to get Attributes - this works for all instance types
@ -64,7 +126,21 @@ local function encodeAttributes(instance, properties)
end
end
encodeInstance = function(instance, parentId)
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
-- For children, the parent already verified the path is unique up to that point,
-- so we only need to check siblings at each level (done in the recursion below)
if not _skipPathCheck and not isPathUnique(instance) then
skippedDuplicateCount += 1
Log.warn(
"Skipped instance '{}' ({}) - path contains duplicate-named siblings (cannot reliably sync)",
instance:GetFullName(),
instance.ClassName
)
return nil
end
local properties = {}
-- Always try to encode Attributes for any instance type
@ -111,10 +187,25 @@ encodeInstance = function(instance, parentId)
end
-- For directory classes (Folder, Configuration, etc.), we only encode Attributes (done above)
-- Recursively encode children
-- Recursively encode children, skipping those with duplicate names
local children = {}
for _, child in ipairs(instance:GetChildren()) do
local encodedChild = encodeInstance(child, nil) -- parentId will be resolved server-side
local childInstances = instance:GetChildren()
local childDuplicates = findDuplicateNames(childInstances)
for _, child in ipairs(childInstances) do
-- Skip children with duplicate names
if childDuplicates[child.Name] then
skippedDuplicateCount += 1
Log.warn(
"Skipped child instance '{}' ({}) - has duplicate-named siblings (cannot reliably sync)",
child:GetFullName(),
child.ClassName
)
continue
end
-- Pass true to skip duplicate check since we already checked above
local encodedChild = encodeInstance(child, nil, true)
if encodedChild then
table.insert(children, encodedChild)
end
@ -129,4 +220,20 @@ encodeInstance = function(instance, parentId)
}
end
return encodeInstance
-- Wrapper function that resets the duplicate count and returns the final count
local function encodeInstanceWithDuplicateTracking(instance, parentId)
skippedDuplicateCount = 0
local result = encodeInstance(instance, parentId, false)
-- Log summary if any duplicates were skipped
if skippedDuplicateCount > 0 then
Log.warn(
"Skipped {} location(s) with duplicate-named siblings during pull (cannot reliably sync)",
skippedDuplicateCount
)
end
return result, skippedDuplicateCount
end
return encodeInstanceWithDuplicateTracking