Enhance scripts-only mode handling and improve temp ID resolution

- Introduced a new mechanism in ServeSession to manage scripts-only mode, ensuring proper handling of temporary IDs during script additions.
- Updated the applyPatch function to remap only scripts-only temp IDs, preventing false positives with legitimate additions.
- Modified the API to resolve intermediate container temp IDs to real tree references, enhancing the integrity of parent-child relationships for new scripts.
- Added tests to verify the functionality of scripts-only mode and the resolution of temp IDs.

These changes aim to optimize script management and improve the overall functionality of the session handling.
This commit is contained in:
Joe 2026-02-28 07:23:49 -06:00
parent dcd48642be
commit 57433a0158
26 changed files with 274 additions and 63 deletions

View file

@ -35,6 +35,10 @@ function InstanceMap.new(onInstanceChanged)
-- Callback invoked when a new instance is inserted into the map.
-- Used by ChangeBatcher to resolve pending unresolved Ref properties.
onInstanceInserted = nil,
-- Temp IDs generated by DescendantAdded watchers in scripts-only mode.
-- Used by applyPatch to distinguish echo additions from legitimate ones.
scriptsOnlyTempIds = {},
}
return setmetatable(self, InstanceMap)

View file

@ -71,24 +71,29 @@ local function applyPatch(instanceMap, patch)
-- When the plugin pre-inserts instances with temp IDs and the server
-- broadcasts the same instances with real Refs, remap the InstanceMap
-- entries rather than creating duplicates via reify.
local remapProgress = true
while remapProgress do
remapProgress = false
for id, virtualInstance in pairs(patch.added) do
local parentInstance = instanceMap.fromIds[virtualInstance.Parent]
if not parentInstance then
continue
end
for _, child in parentInstance:GetChildren() do
if child.Name == virtualInstance.Name and child.ClassName == virtualInstance.ClassName then
local existingId = instanceMap.fromInstances[child]
if existingId and existingId ~= id then
Log.trace("Echo remap: {} -> {} for {}", existingId, id, child:GetFullName())
instanceMap:removeId(existingId)
instanceMap:insert(id, child)
patch.added[id] = nil
remapProgress = true
break
-- Only remap IDs explicitly tracked as scripts-only temp IDs to avoid
-- false positives with legitimate same-named additions.
if next(instanceMap.scriptsOnlyTempIds) then
local remapProgress = true
while remapProgress do
remapProgress = false
for id, virtualInstance in pairs(patch.added) do
local parentInstance = instanceMap.fromIds[virtualInstance.Parent]
if not parentInstance then
continue
end
for _, child in parentInstance:GetChildren() do
if child.Name == virtualInstance.Name and child.ClassName == virtualInstance.ClassName then
local existingId = instanceMap.fromInstances[child]
if existingId and existingId ~= id and instanceMap.scriptsOnlyTempIds[existingId] then
Log.trace("Echo remap: {} -> {} for {}", existingId, id, child:GetFullName())
instanceMap.scriptsOnlyTempIds[existingId] = nil
instanceMap:removeId(existingId)
instanceMap:insert(id, child)
patch.added[id] = nil
remapProgress = true
break
end
end
end
end

View file

@ -906,18 +906,39 @@ function ServeSession:__connectScriptsOnlyWatchers()
return
end
for _, instance in self.__instanceMap.fromIds do
if instance.Parent ~= game then
continue
local services = {}
if self.__serverInfo and self.__serverInfo.visibleServices then
for _, serviceName in ipairs(self.__serverInfo.visibleServices) do
local ok, service = pcall(game.GetService, game, serviceName)
if ok and service then
table.insert(services, service)
end
end
else
for _, instance in self.__instanceMap.fromIds do
if instance.Parent == game then
table.insert(services, instance)
end
end
end
local conn = instance.DescendantAdded:Connect(function(descendant)
for _, service in services do
local conn = service.DescendantAdded:Connect(function(descendant)
if not self.__twoWaySync then
return
end
if self.__status ~= Status.Connected then
return
end
if Settings:get("oneShotSync") then
return
end
if RunService:IsRunning() then
return
end
if self.__changeBatcher:isPaused() then
return
end
if not descendant:IsA("LuaSourceContainer") then
return
end
@ -936,18 +957,26 @@ function ServeSession:__connectScriptsOnlyWatchers()
end
local mappedParent = current
if not mappedParent then
return
end
local mappedParentId = self.__instanceMap.fromInstances[mappedParent]
local mappedParentId = mappedParent and self.__instanceMap.fromInstances[mappedParent]
if not mappedParentId then
return
local rootId = self.__instanceMap.fromInstances[game]
if not rootId then
return
end
table.insert(chain, 1, service)
mappedParentId = rootId
end
local patch = PatchSet.newEmpty()
local prevParentId = mappedParentId
for _, intermediate in chain do
local existingId = self.__instanceMap.fromInstances[intermediate]
if existingId then
prevParentId = existingId
continue
end
local tempId = string.gsub(HttpService:GenerateGUID(false), "-", ""):lower()
patch.added[tempId] = {
parent = prevParentId,
@ -957,6 +986,7 @@ function ServeSession:__connectScriptsOnlyWatchers()
children = {},
}
self.__instanceMap:insert(tempId, intermediate)
self.__instanceMap.scriptsOnlyTempIds[tempId] = true
prevParentId = tempId
end
@ -969,11 +999,13 @@ function ServeSession:__connectScriptsOnlyWatchers()
)
for tempId in patch.added do
self.__instanceMap:removeId(tempId)
self.__instanceMap.scriptsOnlyTempIds[tempId] = nil
end
return
end
patch.added[scriptTempId] = encoded
self.__instanceMap:insert(scriptTempId, descendant)
self.__instanceMap.scriptsOnlyTempIds[scriptTempId] = true
Log.info(
"Scripts-only: detected new script {} outside mapped tree, sending to server",