mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-25 07:06:12 +00:00
plugin: Implement patch application, which makes live sync work
This commit is contained in:
@@ -8,6 +8,7 @@ local t = require(script.Parent.Parent.t)
|
|||||||
|
|
||||||
local InstanceMap = require(script.Parent.InstanceMap)
|
local InstanceMap = require(script.Parent.InstanceMap)
|
||||||
local Types = require(script.Parent.Types)
|
local Types = require(script.Parent.Types)
|
||||||
|
local invariant = require(script.Parent.invariant)
|
||||||
local setCanonicalProperty = require(script.Parent.setCanonicalProperty)
|
local setCanonicalProperty = require(script.Parent.setCanonicalProperty)
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -76,6 +77,96 @@ function Reconciler:hydrate(apiInstances, id, instance)
|
|||||||
return hydratePatch
|
return hydratePatch
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Applies a patch to the Roblox DOM using the reconciler's internal state.
|
||||||
|
|
||||||
|
TODO: This function might only apply some of the patch in the future and
|
||||||
|
require content negotiation with the Rojo server to handle types that aren't
|
||||||
|
editable by scripts.
|
||||||
|
]]
|
||||||
|
local applyPatchSchema = Types.ifEnabled(t.tuple(
|
||||||
|
IPatch
|
||||||
|
))
|
||||||
|
function Reconciler:applyPatch(patch)
|
||||||
|
assert(applyPatchSchema(patch))
|
||||||
|
|
||||||
|
for _, removedIdOrInstance in ipairs(patch.removed) do
|
||||||
|
if Types.RbxId(removedIdOrInstance) then
|
||||||
|
-- If this value is an ID, it's assumed to be an instance that the
|
||||||
|
-- Rojo server knows about.
|
||||||
|
|
||||||
|
self.__instanceMap:removeId(removedIdOrInstance)
|
||||||
|
else
|
||||||
|
-- Otherwise, this instance is one that the Rojo server doesn't know
|
||||||
|
-- about, and is located in an area where it should be destroyed to
|
||||||
|
-- keep our trees in sync.
|
||||||
|
|
||||||
|
removedIdOrInstance:Destroy()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: This loop assumes that apiInstance.ParentId is never nil. The Rojo
|
||||||
|
-- plugin can't create a new top-level DataModel anyways, so this should
|
||||||
|
-- only be violated in cases that are already erroneous.
|
||||||
|
for id, apiInstance in pairs(patch.added) do
|
||||||
|
if self.__instanceMap.fromIds[id] == nil then
|
||||||
|
-- Find the first ancestor of this instance that is marked for an
|
||||||
|
-- addition.
|
||||||
|
--
|
||||||
|
-- This helps us make sure we only reify each instance once, and we
|
||||||
|
-- start from the top.
|
||||||
|
while patch.added[apiInstance.Parent] ~= nil do
|
||||||
|
id = apiInstance.Parent
|
||||||
|
apiInstance = patch.added[id]
|
||||||
|
end
|
||||||
|
|
||||||
|
local parentInstance = self.__instanceMap.fromIds[apiInstance.Parent]
|
||||||
|
|
||||||
|
if parentInstance == nil then
|
||||||
|
invariant(
|
||||||
|
"Cannot add an instance from a patch that has no parent.\nInstance %s with parent %s.\nInstanceMap state:\n%s",
|
||||||
|
id,
|
||||||
|
tostring(apiInstance.Parent),
|
||||||
|
self.__instanceMap:debugState()
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:__reifyInstance(patch.added, id, parentInstance)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, update in ipairs(patch.updated) do
|
||||||
|
local instance = self.__instanceMap.fromIds[update.id]
|
||||||
|
|
||||||
|
if instance == nil then
|
||||||
|
invariant(
|
||||||
|
"Cannot update an instance that does not exist in the reconciler's state.\nInstance ID %s\nInstanceMap state:\n%s",
|
||||||
|
update.id,
|
||||||
|
self.__instanceMap:debugState()
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
if update.changedClassName ~= nil then
|
||||||
|
error("TODO: Support changing class name by destroying + recreating instance.")
|
||||||
|
end
|
||||||
|
|
||||||
|
if update.changedName ~= nil then
|
||||||
|
instance.Name = update.changedName
|
||||||
|
end
|
||||||
|
|
||||||
|
if update.changedMetadata ~= nil then
|
||||||
|
print("TODO: Support changing metadata, if necessary.")
|
||||||
|
end
|
||||||
|
|
||||||
|
if update.changedProperties ~= nil then
|
||||||
|
for propertyName, propertyValue in pairs(update.changedProperties) do
|
||||||
|
-- TODO: Gracefully handle this error instead?
|
||||||
|
assert(setCanonicalProperty(instance, propertyName, self:__decodeApiValue(propertyValue)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Transforms a value encoded by rbx_dom_weak on the server side into a value
|
Transforms a value encoded by rbx_dom_weak on the server side into a value
|
||||||
usable by Rojo's reconciler, potentially using RbxDom.
|
usable by Rojo's reconciler, potentially using RbxDom.
|
||||||
@@ -124,7 +215,7 @@ function Reconciler:__reifySingleInstance(apiInstance)
|
|||||||
setCanonicalProperty(instance, key, self:__decodeApiValue(value))
|
setCanonicalProperty(instance, key, self:__decodeApiValue(value))
|
||||||
end
|
end
|
||||||
|
|
||||||
return instance
|
return true, instance
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -150,12 +241,13 @@ function Reconciler:__reifyInstance(apiInstances, id, parentInstance)
|
|||||||
))
|
))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self.__instanceMap:insert(id, instance)
|
||||||
|
|
||||||
for _, childId in ipairs(apiInstance.Children) do
|
for _, childId in ipairs(apiInstance.Children) do
|
||||||
self:__reify(apiInstances, childId, instance)
|
self:__reifyInstance(apiInstances, childId, instance)
|
||||||
end
|
end
|
||||||
|
|
||||||
safeSetParent(instance, parentInstance)
|
safeSetParent(instance, parentInstance)
|
||||||
self.__instanceMap:insert(id, instance)
|
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
end
|
end
|
||||||
@@ -174,6 +266,8 @@ local hydrateSchema = Types.ifEnabled(t.tuple(
|
|||||||
function Reconciler:__hydrateInternal(apiInstances, id, instance, hydratePatch)
|
function Reconciler:__hydrateInternal(apiInstances, id, instance, hydratePatch)
|
||||||
assert(hydrateSchema(apiInstances, id, instance, hydratePatch))
|
assert(hydrateSchema(apiInstances, id, instance, hydratePatch))
|
||||||
|
|
||||||
|
self.__instanceMap:insert(id, instance)
|
||||||
|
|
||||||
local apiInstance = apiInstances[id]
|
local apiInstance = apiInstances[id]
|
||||||
|
|
||||||
local function markIdAdded(id)
|
local function markIdAdded(id)
|
||||||
@@ -236,7 +330,7 @@ function Reconciler:__hydrateInternal(apiInstances, id, instance, hydratePatch)
|
|||||||
if shouldClearUnknown then
|
if shouldClearUnknown then
|
||||||
for childIndex, visited in ipairs(isExistingChildVisited) do
|
for childIndex, visited in ipairs(isExistingChildVisited) do
|
||||||
if not visited then
|
if not visited then
|
||||||
table.insert(hydratePatch.removedInstances, existingChildren[childIndex])
|
table.insert(hydratePatch.removed, existingChildren[childIndex])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -72,11 +72,40 @@ function ServeSession:start()
|
|||||||
game
|
game
|
||||||
)
|
)
|
||||||
|
|
||||||
DEBUG_printPatch(hydratePatch)
|
-- TODO: Prompt user to notify them of this patch, since
|
||||||
|
-- it's effectively a conflict between the Rojo server and
|
||||||
|
-- the client.
|
||||||
|
|
||||||
-- TODO: Apply the patch generated by hydration. We should
|
self.__reconciler:applyPatch(hydratePatch)
|
||||||
-- eventually prompt the user about this since it's a
|
|
||||||
-- conflict between Rojo and their current place state.
|
-- TODO: Applying a patch may eventually only apply part of
|
||||||
|
-- the patch and start a content negotiation process with
|
||||||
|
-- the Rojo server. We should handle that!
|
||||||
|
|
||||||
|
local function mainLoop()
|
||||||
|
return self.__apiContext:retrieveMessages()
|
||||||
|
:andThen(function(messages)
|
||||||
|
for _, message in ipairs(messages) do
|
||||||
|
-- TODO: Update server to return patches in
|
||||||
|
-- correct format so that we don't have to
|
||||||
|
-- transform them for the reconciler.
|
||||||
|
|
||||||
|
local asPatch = {
|
||||||
|
removed = message.removedInstances,
|
||||||
|
updated = message.updatedInstances,
|
||||||
|
added = message.addedInstances,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.__reconciler:applyPatch(asPatch)
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.__status ~= Status.Disconnected then
|
||||||
|
return mainLoop()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return mainLoop()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
:catch(function(err)
|
:catch(function(err)
|
||||||
|
|||||||
Reference in New Issue
Block a user