Much more robust reconciliation implementation

This commit is contained in:
Lucien Greathouse
2017-12-01 00:53:41 -08:00
parent 5e64773784
commit 2681972976
2 changed files with 158 additions and 81 deletions

View File

@@ -2,6 +2,7 @@ local Config = require(script.Parent.Config)
local Http = require(script.Parent.Http)
local Server = require(script.Parent.Server)
local Promise = require(script.Parent.Promise)
local Reconciler = require(script.Parent.Reconciler)
local function collectMatch(source, pattern)
local result = {}
@@ -13,85 +14,6 @@ local function collectMatch(source, pattern)
return result
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 = {}
Plugin.__index = Plugin
@@ -188,14 +110,14 @@ function Plugin:_pull(server, project, routes)
local route = routes[index]
local partitionName = route[1]
local partition = project.partitions[partitionName]
local data = items[index]
local item = items[index]
local fullRoute = collectMatch(partition.target, "[^.]+")
for i = 2, #route do
table.insert(fullRoute, routes[index][i])
end
write(game, fullRoute, data)
Reconciler.reconcileRoute(fullRoute, item)
end
end

155
plugin/src/Reconciler.lua Normal file
View 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