Add plugin half of script-only, existing-instance-only, two way sync

This commit is contained in:
Lucien Greathouse
2019-12-18 17:39:04 -08:00
parent e83437c193
commit cfff08cdfd
4 changed files with 95 additions and 3 deletions

View File

@@ -1,4 +1,5 @@
local Http = require(script.Parent.Parent.Http) local Http = require(script.Parent.Parent.Http)
local Log = require(script.Parent.Parent.Log)
local Promise = require(script.Parent.Parent.Promise) local Promise = require(script.Parent.Parent.Promise)
local Config = require(script.Parent.Config) local Config = require(script.Parent.Config)
@@ -153,6 +154,23 @@ function ApiContext:read(ids)
end) end)
end end
function ApiContext:write(patch)
local url = ("%s/write"):format(self.__baseUrl)
local body = Http.jsonEncode({
sessionId = self.__sessionId,
patch = patch,
})
return Http.post(url, body)
:andThen(rejectFailedRequests)
:andThen(Http.Response.json)
:andThen(function(body)
Log.info("Write response: {:?}", body)
return body
end)
end
function ApiContext:retrieveMessages() function ApiContext:retrieveMessages()
local url = ("%s/api/subscribe/%s"):format(self.__baseUrl, self.__messageCursor) local url = ("%s/api/subscribe/%s"):format(self.__baseUrl, self.__messageCursor)

View File

@@ -25,6 +25,14 @@ local VALUES = {
[Environment.Test] = true, [Environment.Test] = true,
}, },
}, },
ExperimentalTwoWaySync = {
type = "BoolValue",
values = {
[Environment.User] = false,
[Environment.Dev] = false,
[Environment.Test] = false,
},
},
} }
local CONTAINER_NAME = "RojoDevSettings" .. Config.codename local CONTAINER_NAME = "RojoDevSettings" .. Config.codename
@@ -132,6 +140,10 @@ function DevSettings:shouldTypecheck()
return getValue("TypecheckingEnabled") return getValue("TypecheckingEnabled")
end end
function DevSettings:twoWaySyncEnabled()
return getValue("ExperimentalTwoWaySync")
end
function _G.ROJO_DEV_CREATE() function _G.ROJO_DEV_CREATE()
DevSettings:createDevSettings() DevSettings:createDevSettings()
end end

View File

@@ -176,6 +176,23 @@ function Reconciler:applyPatch(patch)
end end
end end
--[[
Transforms a value into one that can be sent over the network back to the
Rojo server.
This operation can fail, and so it returns bool, value.
]]
function Reconciler:encodeApiValue(value)
if typeof(value) == "string" then
return true, {
Type = "String",
Value = value,
}
end
return false
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.
@@ -302,7 +319,6 @@ function Reconciler:__hydrateInternal(apiInstances, id, instance, hydratePatch)
local decodedValue = self:__decodeApiValue(virtualValue) local decodedValue = self:__decodeApiValue(virtualValue)
if existingValue ~= decodedValue then if existingValue ~= decodedValue then
Log.warn("Diff! {:?} vs {:?}", existingValue, decodedValue)
changedProperties[propertyName] = virtualValue changedProperties[propertyName] = virtualValue
end end
end end

View File

@@ -2,6 +2,7 @@ local Log = require(script.Parent.Parent.Log)
local Fmt = require(script.Parent.Parent.Fmt) local Fmt = require(script.Parent.Parent.Fmt)
local t = require(script.Parent.Parent.t) local t = require(script.Parent.Parent.t)
local DevSettings = require(script.Parent.DevSettings)
local InstanceMap = require(script.Parent.InstanceMap) local InstanceMap = require(script.Parent.InstanceMap)
local Reconciler = require(script.Parent.Reconciler) local Reconciler = require(script.Parent.Reconciler)
local strict = require(script.Parent.strict) local strict = require(script.Parent.strict)
@@ -47,10 +48,16 @@ local validateServeOptions = t.strictInterface({
function ServeSession.new(options) function ServeSession.new(options)
assert(validateServeOptions(options)) assert(validateServeOptions(options))
local instanceMap = InstanceMap.new() -- Declare self ahead of time to capture it in a closure
local self
local function onInstanceChanged(instance, propertyName)
self:__onInstanceChanged(instance, propertyName)
end
local instanceMap = InstanceMap.new(onInstanceChanged)
local reconciler = Reconciler.new(instanceMap) local reconciler = Reconciler.new(instanceMap)
local self = { self = {
__status = Status.NotStarted, __status = Status.NotStarted,
__apiContext = options.apiContext, __apiContext = options.apiContext,
__reconciler = reconciler, __reconciler = reconciler,
@@ -101,6 +108,45 @@ function ServeSession:stop()
self:__stopInternal() self:__stopInternal()
end end
function ServeSession:__onInstanceChanged(instance, propertyName)
if not DevSettings:twoWaySyncEnabled() then
return
end
local instanceId = self.__instanceMap.fromInstances[instance]
if instanceId == nil then
Log.warn("Ignoring change for instance {:?} as it is unknown to Rojo", instance)
return
end
local update = {
id = instanceId,
changedProperties = {},
}
if propertyName == "Name" then
update.changedName = instance.Name
else
local success, encoded = self.__reconciler:encodeApiValue(instance[propertyName])
if not success then
Log.warn("Could not sync back property {:?}.{}", instance, propertyName)
return
end
update.changedProperties[propertyName] = encoded
end
local patch = {
removed = {},
added = {},
updated = {update},
}
self.__apiContext:write(patch)
end
function ServeSession:__initialSync(rootInstanceId) function ServeSession:__initialSync(rootInstanceId)
return self.__apiContext:read({ rootInstanceId }) return self.__apiContext:read({ rootInstanceId })
:andThen(function(readResponseBody) :andThen(function(readResponseBody)