rojo/plugin/src/ServeSession.lua
2019-10-14 14:05:25 -07:00

136 lines
No EOL
3.2 KiB
Lua

local t = require(script.Parent.Parent.t)
local strict = require(script.Parent.strict)
local Status = strict("Session.Status", {
NotStarted = "NotStarted",
Connecting = "Connecting",
Connected = "Connected",
Disconnected = "Disconnected",
})
local function DEBUG_printPatch(patch)
local HttpService = game:GetService("HttpService")
for removed in ipairs(patch.removed) do
print("Remove:", removed)
end
for id, added in pairs(patch.added) do
print("Add:", id, HttpService:JSONEncode(added))
end
for updated in ipairs(patch.updated) do
print("Update:", HttpService:JSONEncode(updated))
end
end
local ServeSession = {}
ServeSession.__index = ServeSession
ServeSession.Status = Status
local validateServeOptions = t.strictInterface({
apiContext = t.table,
reconciler = t.table,
})
function ServeSession.new(options)
assert(validateServeOptions(options))
local self = {
__status = Status.NotStarted,
__apiContext = options.apiContext,
__reconciler = options.reconciler,
__statusChangedCallback = nil,
}
setmetatable(self, ServeSession)
return self
end
function ServeSession:onStatusChanged(callback)
self.__statusChangedCallback = callback
end
function ServeSession:start()
self:__setStatus(Status.Connecting)
self.__apiContext:connect()
:andThen(function(serverInfo)
self:__setStatus(Status.Connected)
local rootInstanceId = serverInfo.rootInstanceId
return self:__initialSync(rootInstanceId)
:andThen(function()
return self:__mainSyncLoop()
end)
end)
:catch(function(err)
self:__stopInternal(err)
end)
end
function ServeSession:stop()
self:__stopInternal()
end
function ServeSession:__initialSync(rootInstanceId)
return self.__apiContext:read({ rootInstanceId })
:andThen(function(readResponseBody)
-- Tell the API Context that we're up-to-date with the version of
-- the tree defined in this response.
self.__apiContext:setMessageCursor(readResponseBody.messageCursor)
-- Calculate the initial patch to apply to the DataModel to catch us
-- up to what Rojo thinks the place should look like.
local hydratePatch = self.__reconciler:hydrate(
readResponseBody.instances,
rootInstanceId,
game
)
-- TODO: Prompt user to notify them of this patch, since it's
-- effectively a conflict between the Rojo server and the client.
self.__reconciler:applyPatch(hydratePatch)
end)
end
function ServeSession:__mainSyncLoop()
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 self:__mainSyncLoop()
end
end)
end
function ServeSession:__stopInternal(err)
self:__setStatus(Status.Disconnected, err)
self.__apiContext:disconnect()
end
function ServeSession:__setStatus(status, detail)
self.__status = status
if self.__statusChangedCallback ~= nil then
self.__statusChangedCallback(status, detail)
end
end
return ServeSession