From 22e437a055a39d67fefc9d4039f1fdd9cfe362f4 Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 28 Feb 2026 12:00:01 -0600 Subject: [PATCH] Implement fallback error handling and enhance scripts-only mode in ServeSession - Added a fallback mechanism in the `__applyPatch` method to handle errors during patch application, logging warnings for any encountered issues. - Improved the transitive closure logic in the `__connectScriptsOnlyWatchers` method to ensure all necessary temporary IDs are included as skeleton containers, enhancing the integrity of the synchronization process. These changes aim to improve error resilience and ensure proper handling of script references in scripts-only mode. --- plugin/src/ServeSession.lua | 88 ++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 21 deletions(-) diff --git a/plugin/src/ServeSession.lua b/plugin/src/ServeSession.lua index 47b7d477..1f2ec64d 100644 --- a/plugin/src/ServeSession.lua +++ b/plugin/src/ServeSession.lua @@ -574,33 +574,39 @@ function ServeSession:__applyPatch(patch) error(unappliedPatch) end - if Settings:get("enableSyncFallback") and not PatchSet.isEmpty(unappliedPatch) then - -- Some changes did not apply, let's try replacing them instead - local addedIdList = PatchSet.addedIdList(unappliedPatch) - local updatedIdList = PatchSet.updatedIdList(unappliedPatch) + local fallbackOk, fallbackErr = pcall(function() + if Settings:get("enableSyncFallback") and not PatchSet.isEmpty(unappliedPatch) then + -- Some changes did not apply, let's try replacing them instead + local addedIdList = PatchSet.addedIdList(unappliedPatch) + local updatedIdList = PatchSet.updatedIdList(unappliedPatch) - Log.debug("ServeSession:__replaceInstances(unappliedPatch.added)") - Timer.start("ServeSession:__replaceInstances(unappliedPatch.added)") - local addSuccess, unappliedAddedRefs = self:__replaceInstances(addedIdList) - Timer.stop() + Log.debug("ServeSession:__replaceInstances(unappliedPatch.added)") + Timer.start("ServeSession:__replaceInstances(unappliedPatch.added)") + local addSuccess, unappliedAddedRefs = self:__replaceInstances(addedIdList) + Timer.stop() - Log.debug("ServeSession:__replaceInstances(unappliedPatch.updated)") - Timer.start("ServeSession:__replaceInstances(unappliedPatch.updated)") - local updateSuccess, unappliedUpdateRefs = self:__replaceInstances(updatedIdList) - Timer.stop() + Log.debug("ServeSession:__replaceInstances(unappliedPatch.updated)") + Timer.start("ServeSession:__replaceInstances(unappliedPatch.updated)") + local updateSuccess, unappliedUpdateRefs = self:__replaceInstances(updatedIdList) + Timer.stop() - -- Update the unapplied patch to reflect which Instances were replaced successfully - if addSuccess then - table.clear(unappliedPatch.added) - PatchSet.assign(unappliedPatch, unappliedAddedRefs) + -- Update the unapplied patch to reflect which Instances were replaced successfully + if addSuccess then + table.clear(unappliedPatch.added) + PatchSet.assign(unappliedPatch, unappliedAddedRefs) + end + if updateSuccess then + table.clear(unappliedPatch.updated) + PatchSet.assign(unappliedPatch, unappliedUpdateRefs) + end end - if updateSuccess then - table.clear(unappliedPatch.updated) - PatchSet.assign(unappliedPatch, unappliedUpdateRefs) - end - end + end) self.__applyingPatch = false + if not fallbackOk then + Log.warn("Sync fallback errored: {}", fallbackErr) + end + if not PatchSet.isEmpty(unappliedPatch) then Log.info( "Could not apply all changes requested by the Rojo server:\n{}", @@ -1014,6 +1020,46 @@ function ServeSession:__connectScriptsOnlyWatchers() self.__instanceMap:insert(scriptTempId, descendant) self.__instanceMap.scriptsOnlyTempIds[scriptTempId] = true + -- Transitive closure: ensure every temp-ID parent referenced by + -- a patch entry is itself included as a skeleton container. When + -- multiple scripts are created in the same pruned subtree (e.g., + -- paste), later handlers find intermediates already in instanceMap + -- with temp IDs from earlier handlers. Without re-including those + -- skeletons, the server's Phase 0 can't resolve the temp parents. + local closureNeeded = true + while closureNeeded do + closureNeeded = false + local toAdd = {} + for _, entry in pairs(patch.added) do + local pid = entry.parent + if + pid + and self.__instanceMap.scriptsOnlyTempIds[pid] + and not patch.added[pid] + and not toAdd[pid] + then + local inst = self.__instanceMap.fromIds[pid] + if inst then + local pp = inst.Parent + local ppId = pp and self.__instanceMap.fromInstances[pp] + if ppId then + toAdd[pid] = { + parent = ppId, + name = inst.Name, + className = inst.ClassName, + properties = {}, + children = {}, + } + closureNeeded = true + end + end + end + end + for id, entry in toAdd do + patch.added[id] = entry + end + end + Log.info( "Scripts-only: detected new script {} outside mapped tree, sending to server", descendant:GetFullName()