mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-23 14:15:24 +00:00
Add plugin half of script-only, existing-instance-only, two way sync
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user