forked from rojo-rbx/rojo
merge impl-v2: plugin
This commit is contained in:
@@ -1,246 +0,0 @@
|
||||
local RouteMap = require(script.Parent.RouteMap)
|
||||
|
||||
local function classEqual(a, b)
|
||||
assert(typeof(a) == "string")
|
||||
assert(typeof(b) == "string")
|
||||
|
||||
if a == "*" or b == "*" then
|
||||
return true
|
||||
end
|
||||
|
||||
return a == b
|
||||
end
|
||||
|
||||
local function applyProperties(target, properties)
|
||||
assert(typeof(target) == "Instance")
|
||||
assert(typeof(properties) == "table")
|
||||
|
||||
for key, property in pairs(properties) do
|
||||
-- TODO: Transform property value based on property.Type
|
||||
-- Right now, we assume that 'value' is primitive!
|
||||
target[key] = property.Value
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Attempt to parent `rbx` to `parent`, doing nothing if:
|
||||
* parent is already `parent`
|
||||
* Changing parent threw an error
|
||||
]]
|
||||
local function reparent(rbx, parent)
|
||||
assert(typeof(rbx) == "Instance")
|
||||
assert(typeof(parent) == "Instance")
|
||||
|
||||
if rbx.Parent == parent then
|
||||
return
|
||||
end
|
||||
|
||||
-- Setting `Parent` can fail if:
|
||||
-- * The object has been destroyed
|
||||
-- * The object is a service and cannot be reparented
|
||||
pcall(function()
|
||||
rbx.Parent = parent
|
||||
end)
|
||||
end
|
||||
|
||||
--[[
|
||||
Attempts to match up Roblox instances and object specifiers for
|
||||
reconciliation.
|
||||
|
||||
An object is considered a match if they have the same Name and ClassName.
|
||||
|
||||
primaryChildren and secondaryChildren can each be either a list of Roblox
|
||||
instances or object specifiers. Since they share a common shape, switching
|
||||
the two around isn't problematic!
|
||||
|
||||
visited is expected to be an empty table initially. It will be filled with
|
||||
the set of children that have been visited so far.
|
||||
]]
|
||||
local function findNextChildPair(primaryChildren, secondaryChildren, visited)
|
||||
for _, primaryChild in ipairs(primaryChildren) do
|
||||
if not visited[primaryChild] then
|
||||
visited[primaryChild] = true
|
||||
|
||||
for _, secondaryChild in ipairs(secondaryChildren) do
|
||||
if classEqual(primaryChild.ClassName, secondaryChild.ClassName) and primaryChild.Name == secondaryChild.Name then
|
||||
visited[secondaryChild] = true
|
||||
|
||||
return primaryChild, secondaryChild
|
||||
end
|
||||
end
|
||||
|
||||
return primaryChild, nil
|
||||
end
|
||||
end
|
||||
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local Reconciler = {}
|
||||
Reconciler.__index = Reconciler
|
||||
|
||||
function Reconciler.new()
|
||||
local reconciler = {
|
||||
_routeMap = RouteMap.new(),
|
||||
}
|
||||
|
||||
setmetatable(reconciler, Reconciler)
|
||||
|
||||
return reconciler
|
||||
end
|
||||
|
||||
--[[
|
||||
A semi-smart algorithm that attempts to apply the given item's children to
|
||||
an existing Roblox object.
|
||||
]]
|
||||
function Reconciler:_reconcileChildren(rbx, item)
|
||||
local visited = {}
|
||||
local rbxChildren = rbx:GetChildren()
|
||||
|
||||
-- Reconcile any children that were added or updated
|
||||
while true do
|
||||
local itemChild, rbxChild = findNextChildPair(item.Children, rbxChildren, visited)
|
||||
|
||||
if itemChild == nil then
|
||||
break
|
||||
end
|
||||
|
||||
local newRbxChild = self:reconcile(rbxChild, itemChild)
|
||||
|
||||
if newRbxChild ~= nil then
|
||||
newRbxChild.Parent = rbx
|
||||
end
|
||||
end
|
||||
|
||||
-- Reconcile any children that were deleted
|
||||
while true do
|
||||
local rbxChild, itemChild = findNextChildPair(rbxChildren, item.Children, visited)
|
||||
|
||||
if rbxChild == nil then
|
||||
break
|
||||
end
|
||||
|
||||
local newRbxChild = self:reconcile(rbxChild, itemChild)
|
||||
|
||||
if newRbxChild ~= nil then
|
||||
newRbxChild.Parent = rbx
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Construct a new Roblox object from the given item.
|
||||
]]
|
||||
function Reconciler:_reify(item)
|
||||
local className = item.ClassName
|
||||
|
||||
-- "*" represents a match of any class. It reifies as a folder!
|
||||
if className == "*" then
|
||||
className = "Folder"
|
||||
end
|
||||
|
||||
local rbx = Instance.new(className)
|
||||
rbx.Name = item.Name
|
||||
|
||||
applyProperties(rbx, item.Properties)
|
||||
|
||||
for _, child in ipairs(item.Children) do
|
||||
reparent(self:_reify(child), rbx)
|
||||
end
|
||||
|
||||
if item.Route ~= nil then
|
||||
self._routeMap:insert(item.Route, rbx)
|
||||
end
|
||||
|
||||
return rbx
|
||||
end
|
||||
|
||||
--[[
|
||||
Clears any state that the Reconciler has, stopping it completely.
|
||||
]]
|
||||
function Reconciler:destruct()
|
||||
self._routeMap:destruct()
|
||||
end
|
||||
|
||||
--[[
|
||||
Apply the changes represented by the given item to a Roblox object that's a
|
||||
child of the given instance.
|
||||
]]
|
||||
function Reconciler:reconcile(rbx, item)
|
||||
-- Item was deleted
|
||||
if item == nil then
|
||||
if rbx ~= nil then
|
||||
self._routeMap:removeByRbx(rbx)
|
||||
rbx:Destroy()
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Item was created!
|
||||
if rbx == nil then
|
||||
return self:_reify(item)
|
||||
end
|
||||
|
||||
-- Item changed type!
|
||||
if not classEqual(rbx.ClassName, item.ClassName) then
|
||||
self._routeMap:removeByRbx(rbx)
|
||||
rbx:Destroy()
|
||||
|
||||
return self:_reify(item)
|
||||
end
|
||||
|
||||
-- It's possible that the instance we're associating with this item hasn't
|
||||
-- been inserted into the RouteMap yet.
|
||||
if item.Route ~= nil then
|
||||
self._routeMap:insert(item.Route, rbx)
|
||||
end
|
||||
|
||||
applyProperties(rbx, item.Properties)
|
||||
self:_reconcileChildren(rbx, item)
|
||||
|
||||
return rbx
|
||||
end
|
||||
|
||||
function Reconciler:reconcileRoute(rbxRoute, item, fileRoute)
|
||||
local parent
|
||||
local rbx = game
|
||||
|
||||
for i = 1, #rbxRoute do
|
||||
local piece = rbxRoute[i]
|
||||
|
||||
local child = rbx:FindFirstChild(piece)
|
||||
|
||||
-- We should get services instead of making folders here.
|
||||
if rbx == game and child == nil then
|
||||
local success
|
||||
success, child = pcall(game.GetService, game, piece)
|
||||
|
||||
-- That isn't a valid service!
|
||||
if not success then
|
||||
child = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- We don't want to create a folder if we're reaching our target item!
|
||||
if child == nil and i ~= #rbxRoute then
|
||||
child = Instance.new("Folder")
|
||||
child.Parent = rbx
|
||||
child.Name = piece
|
||||
end
|
||||
|
||||
parent = rbx
|
||||
rbx = child
|
||||
end
|
||||
|
||||
-- Let's check the route map!
|
||||
if rbx == nil then
|
||||
rbx = self._routeMap:get(fileRoute)
|
||||
end
|
||||
|
||||
rbx = self:reconcile(rbx, item)
|
||||
|
||||
reparent(rbx, parent)
|
||||
end
|
||||
|
||||
return Reconciler
|
||||
Reference in New Issue
Block a user