local Reconciler = {} --[[ The set of file names that should pass as init files These files usurp their parents. ]] local initNames = { ["init.lua"] = true, ["init.server.lua"] = true, ["init.client.lua"] = true, } local function isInit(item, itemFileName) if item and item.type == "dir" then return end return initNames[itemFileName] or false end --[[ Determines if the given VFS item has an init file. Yields information about the file. ]] local function findInit(item) if item.type ~= "dir" then return nil, nil end for childFileName, childItem in pairs(item.children) do if isInit(childItem, childFileName) then return childItem, childFileName end end return nil, nil end --[[ Given a VFS item, returns a Name and ClassName for a corresponding Roblox instance. Doesn't take into account init files. ]] local function itemToName(item, fileName) if item and item.type == "dir" then return fileName, "Folder" elseif item and item.type == "file" or not item then if fileName:find("%.server%.lua$") then return fileName:match("^(.-)%.server%.lua$"), "Script" elseif fileName:find("%.client%.lua$") then return fileName:match("^(.-)%.client%.lua$"), "LocalScript" elseif fileName:find("%.lua") then return fileName:match("^(.-)%.lua$"), "ModuleScript" else return fileName, "StringValue" end else error("unknown item type " .. tostring(item.type)) end end --[[ Given a VFS item, assigns all relevant values (except Name!) to a Roblox instance. ]] local function setValues(rbx, item, fileName) local _, className = itemToName(item, fileName) if className:find("Script") then rbx.Source = item.contents else rbx.Value = item.contents end end function Reconciler._reifyShallow(item, fileName) if item.type == "dir" then local initItem, initFileName = findInit(item) if initItem then local rbx = Reconciler._reify(initItem, initFileName) rbx.Name = fileName return rbx else local rbx = Instance.new("Folder") rbx.Name = fileName return rbx end elseif item.type == "file" then local objectName, className = itemToName(item, fileName) local rbx = Instance.new(className) rbx.Name = objectName setValues(rbx, item, fileName) return rbx else error("unknown item type " .. tostring(item.type)) end end --[[ Construct a new Roblox instance tree that corresponds to the given VFS item. ]] function Reconciler._reify(item, fileName, parent) local rbx = Reconciler._reifyShallow(item, fileName) if item.type == "dir" then for childFileName, childItem in pairs(item.children) do if not isInit(childItem, childFileName) then local childRbx = Reconciler._reify(childItem, childFileName) childRbx.Parent = rbx end end end rbx.Parent = parent return rbx end function Reconciler.reconcile(rbx, item, fileName, parent) -- Item was deleted! if not item then if isInit(item, fileName) then if not parent then return end -- Un-usurp parent! local newParent = Instance.new("Folder") newParent.Name = parent.Name for _, child in ipairs(parent:GetChildren()) do child.Parent = newParent end newParent.Parent = parent.Parent parent:Destroy() return else if rbx then rbx:Destroy() end return end end if item.type == "dir" then -- Folder was created! if not rbx then return Reconciler._reify(item, fileName, parent) end local initItem, initFileName = findInit(item) if initItem then local _, initClassName = itemToName(initItem, initFileName) if rbx.ClassName == initClassName then setValues(rbx, initItem, initFileName) else rbx:Destroy() return Reconciler._reify(item, fileName, parent) end else if rbx.ClassName ~= "Folder" then rbx:Destroy() return Reconciler._reify(item, fileName, parent) end end local visitedChildren = {} for childFileName, childItem in pairs(item.children) do if not isInit(childItem, childFileName) then local childName = itemToName(childItem, childFileName) visitedChildren[childName] = true Reconciler.reconcile(rbx:FindFirstChild(childName), childItem, childFileName, rbx) end end for _, childRbx in ipairs(rbx:GetChildren()) do -- Child was deleted! if not visitedChildren[childRbx.Name] then childRbx:Destroy() end end return rbx elseif item.type == "file" then if isInit(item, fileName) then -- Usurp our container! local _, className = itemToName(item, fileName) if parent.ClassName == className then rbx = parent else rbx = Reconciler._reify(item, fileName, parent.Parent) rbx.Name = parent.Name for _, child in ipairs(parent:GetChildren()) do child.Parent = rbx end parent:Destroy() end setValues(rbx, item, fileName) return rbx else if not rbx then return Reconciler._reify(item, fileName, parent) end local _, className = itemToName(item, fileName) if rbx.ClassName ~= className then rbx:Destroy() return Reconciler._reify(item, fileName, parent) end setValues(rbx, item, fileName) return rbx end else error("unknown item type " .. tostring(item.type)) end end function Reconciler.reconcileRoute(route, item) local location = game for i = 1, #route - 1 do local piece = route[i] local newLocation = location:FindFirstChild(piece) if not newLocation then newLocation = Instance.new("Folder") newLocation.Name = piece newLocation.Parent = location end location = newLocation end local fileName = route[#route] local name = itemToName(item, fileName) local rbx = location:FindFirstChild(name) Reconciler.reconcile(rbx, item, fileName, location) end return Reconciler