mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-26 15:46:28 +00:00
Much more robust reconciliation implementation
This commit is contained in:
@@ -2,6 +2,7 @@ local Config = require(script.Parent.Config)
|
|||||||
local Http = require(script.Parent.Http)
|
local Http = require(script.Parent.Http)
|
||||||
local Server = require(script.Parent.Server)
|
local Server = require(script.Parent.Server)
|
||||||
local Promise = require(script.Parent.Promise)
|
local Promise = require(script.Parent.Promise)
|
||||||
|
local Reconciler = require(script.Parent.Reconciler)
|
||||||
|
|
||||||
local function collectMatch(source, pattern)
|
local function collectMatch(source, pattern)
|
||||||
local result = {}
|
local result = {}
|
||||||
@@ -13,85 +14,6 @@ local function collectMatch(source, pattern)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
local function fileToName(filename)
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
local function nameToInstance(filename, contents)
|
|
||||||
local name, className = fileToName(filename)
|
|
||||||
|
|
||||||
local instance = Instance.new(className)
|
|
||||||
instance.Name = name
|
|
||||||
|
|
||||||
if className:find("Script$") then
|
|
||||||
instance.Source = contents
|
|
||||||
else
|
|
||||||
instance.Value = contents
|
|
||||||
end
|
|
||||||
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
local function make(item, name)
|
|
||||||
if item.type == "dir" then
|
|
||||||
local instance = Instance.new("Folder")
|
|
||||||
instance.Name = name
|
|
||||||
|
|
||||||
for childName, child in pairs(item.children) do
|
|
||||||
make(child, childName).Parent = instance
|
|
||||||
end
|
|
||||||
|
|
||||||
return instance
|
|
||||||
elseif item.type == "file" then
|
|
||||||
return nameToInstance(name, item.contents)
|
|
||||||
else
|
|
||||||
error("not implemented")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function write(parent, route, item)
|
|
||||||
local location = parent
|
|
||||||
|
|
||||||
for index = 1, #route - 1 do
|
|
||||||
local piece = route[index]
|
|
||||||
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 = fileToName(fileName)
|
|
||||||
|
|
||||||
local existing = location:FindFirstChild(name)
|
|
||||||
|
|
||||||
local new
|
|
||||||
if item then
|
|
||||||
new = make(item, fileName)
|
|
||||||
end
|
|
||||||
|
|
||||||
if existing then
|
|
||||||
existing:Destroy()
|
|
||||||
end
|
|
||||||
|
|
||||||
if new then
|
|
||||||
new.Parent = location
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local Plugin = {}
|
local Plugin = {}
|
||||||
Plugin.__index = Plugin
|
Plugin.__index = Plugin
|
||||||
|
|
||||||
@@ -188,14 +110,14 @@ function Plugin:_pull(server, project, routes)
|
|||||||
local route = routes[index]
|
local route = routes[index]
|
||||||
local partitionName = route[1]
|
local partitionName = route[1]
|
||||||
local partition = project.partitions[partitionName]
|
local partition = project.partitions[partitionName]
|
||||||
local data = items[index]
|
local item = items[index]
|
||||||
|
|
||||||
local fullRoute = collectMatch(partition.target, "[^.]+")
|
local fullRoute = collectMatch(partition.target, "[^.]+")
|
||||||
for i = 2, #route do
|
for i = 2, #route do
|
||||||
table.insert(fullRoute, routes[index][i])
|
table.insert(fullRoute, routes[index][i])
|
||||||
end
|
end
|
||||||
|
|
||||||
write(game, fullRoute, data)
|
Reconciler.reconcileRoute(fullRoute, item)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
155
plugin/src/Reconciler.lua
Normal file
155
plugin/src/Reconciler.lua
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
local Reconciler = {}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
-- TODO: handle init
|
||||||
|
local rbx = Instance.new("Folder")
|
||||||
|
rbx.Name = fileName
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
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
|
||||||
|
|
||||||
|
function Reconciler._reify(item, fileName)
|
||||||
|
local rbx = Reconciler._reifyShallow(item, fileName)
|
||||||
|
|
||||||
|
if item.type == "dir" then
|
||||||
|
for childName, child in pairs(item.children) do
|
||||||
|
local childRbx = Reconciler._reify(child, childName)
|
||||||
|
childRbx.Parent = rbx
|
||||||
|
|
||||||
|
-- TODO: handle init
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
end
|
||||||
|
|
||||||
|
function Reconciler.reconcile(rbx, item, fileName)
|
||||||
|
-- Item was deleted!
|
||||||
|
if not item then
|
||||||
|
if rbx then
|
||||||
|
rbx:Destroy()
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Item was created!
|
||||||
|
if not rbx then
|
||||||
|
return Reconciler._reify(item, fileName)
|
||||||
|
end
|
||||||
|
|
||||||
|
if item.type == "dir" then
|
||||||
|
-- TODO: handle init
|
||||||
|
|
||||||
|
if rbx.ClassName ~= "Folder" then
|
||||||
|
return Reconciler._reify(item, fileName)
|
||||||
|
end
|
||||||
|
|
||||||
|
local visitedChildren = {}
|
||||||
|
|
||||||
|
for childFileName, childItem in pairs(item.children) do
|
||||||
|
local childName = itemToName(childItem, childFileName)
|
||||||
|
|
||||||
|
visitedChildren[childName] = true
|
||||||
|
|
||||||
|
Reconciler.reconcile(rbx:FindFirstChild(childName), childItem, childFileName)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, childRbx in ipairs(rbx:GetChildren()) do
|
||||||
|
-- Child was deleted!
|
||||||
|
if not visitedChildren[childRbx.Name] then
|
||||||
|
Reconciler.reconcile(childRbx, nil, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
elseif item.type == "file" then
|
||||||
|
local _, className = itemToName(item, fileName)
|
||||||
|
|
||||||
|
if rbx.ClassName ~= className then
|
||||||
|
return Reconciler._reify(item, fileName)
|
||||||
|
end
|
||||||
|
|
||||||
|
setValues(rbx, item, fileName)
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
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)
|
||||||
|
local newRbx = Reconciler.reconcile(rbx, item, fileName)
|
||||||
|
|
||||||
|
if newRbx ~= rbx then
|
||||||
|
if rbx then
|
||||||
|
rbx:Destroy()
|
||||||
|
end
|
||||||
|
|
||||||
|
if newRbx then
|
||||||
|
newRbx.Parent = location
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Reconciler
|
||||||
Reference in New Issue
Block a user