From cfff08cdfdb9e60e2be9c76ca33dadd54e88aca4 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Wed, 18 Dec 2019 17:39:04 -0800 Subject: [PATCH] Add plugin half of script-only, existing-instance-only, two way sync --- plugin/src/ApiContext.lua | 18 +++++++++++++ plugin/src/DevSettings.lua | 12 +++++++++ plugin/src/Reconciler.lua | 18 ++++++++++++- plugin/src/ServeSession.lua | 50 +++++++++++++++++++++++++++++++++++-- 4 files changed, 95 insertions(+), 3 deletions(-) diff --git a/plugin/src/ApiContext.lua b/plugin/src/ApiContext.lua index 32cea89b..d1c2feed 100644 --- a/plugin/src/ApiContext.lua +++ b/plugin/src/ApiContext.lua @@ -1,4 +1,5 @@ local Http = require(script.Parent.Parent.Http) +local Log = require(script.Parent.Parent.Log) local Promise = require(script.Parent.Parent.Promise) local Config = require(script.Parent.Config) @@ -153,6 +154,23 @@ function ApiContext:read(ids) 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() local url = ("%s/api/subscribe/%s"):format(self.__baseUrl, self.__messageCursor) diff --git a/plugin/src/DevSettings.lua b/plugin/src/DevSettings.lua index f1458c00..182769da 100644 --- a/plugin/src/DevSettings.lua +++ b/plugin/src/DevSettings.lua @@ -25,6 +25,14 @@ local VALUES = { [Environment.Test] = true, }, }, + ExperimentalTwoWaySync = { + type = "BoolValue", + values = { + [Environment.User] = false, + [Environment.Dev] = false, + [Environment.Test] = false, + }, + }, } local CONTAINER_NAME = "RojoDevSettings" .. Config.codename @@ -132,6 +140,10 @@ function DevSettings:shouldTypecheck() return getValue("TypecheckingEnabled") end +function DevSettings:twoWaySyncEnabled() + return getValue("ExperimentalTwoWaySync") +end + function _G.ROJO_DEV_CREATE() DevSettings:createDevSettings() end diff --git a/plugin/src/Reconciler.lua b/plugin/src/Reconciler.lua index 36c19037..6050a401 100644 --- a/plugin/src/Reconciler.lua +++ b/plugin/src/Reconciler.lua @@ -176,6 +176,23 @@ function Reconciler:applyPatch(patch) 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 usable by Rojo's reconciler, potentially using RbxDom. @@ -302,7 +319,6 @@ function Reconciler:__hydrateInternal(apiInstances, id, instance, hydratePatch) local decodedValue = self:__decodeApiValue(virtualValue) if existingValue ~= decodedValue then - Log.warn("Diff! {:?} vs {:?}", existingValue, decodedValue) changedProperties[propertyName] = virtualValue end end diff --git a/plugin/src/ServeSession.lua b/plugin/src/ServeSession.lua index 9f33814c..e6cdbb2f 100644 --- a/plugin/src/ServeSession.lua +++ b/plugin/src/ServeSession.lua @@ -2,6 +2,7 @@ local Log = require(script.Parent.Parent.Log) local Fmt = require(script.Parent.Parent.Fmt) local t = require(script.Parent.Parent.t) +local DevSettings = require(script.Parent.DevSettings) local InstanceMap = require(script.Parent.InstanceMap) local Reconciler = require(script.Parent.Reconciler) local strict = require(script.Parent.strict) @@ -47,10 +48,16 @@ local validateServeOptions = t.strictInterface({ function ServeSession.new(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 self = { + self = { __status = Status.NotStarted, __apiContext = options.apiContext, __reconciler = reconciler, @@ -101,6 +108,45 @@ function ServeSession:stop() self:__stopInternal() 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) return self.__apiContext:read({ rootInstanceId }) :andThen(function(readResponseBody)