mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 14:01:37 +00:00
Implement live syncback feature and enhance API for service data transfer
- Added a new `performSyncback` function in the `App` module to handle the synchronization of service data from the game to the server. - Introduced a `SyncbackConfirm` UI component to confirm syncback actions from the user. - Updated the API to include a new `/api/syncback` endpoint for handling syncback requests, validating protocol and server versions. - Enhanced the `SyncbackSignal` mechanism to facilitate communication between the server and the syncback process. - Added tests to ensure the functionality of the live syncback feature and its integration with the CLI. These changes improve the overall synchronization capabilities of the application, allowing for real-time updates and better project management.
This commit is contained in:
parent
6f8e5a5cf2
commit
307ac81aec
17 changed files with 1411 additions and 97 deletions
|
|
@ -1,8 +1,16 @@
|
|||
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
||||
local Players = game:GetService("Players")
|
||||
local SerializationService = game:GetService("SerializationService")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local SYNCBACK_SERVICES = {
|
||||
"Lighting", "MaterialService", "ReplicatedFirst", "ReplicatedStorage",
|
||||
"ServerScriptService", "ServerStorage", "SoundService", "StarterGui",
|
||||
"StarterPack", "StarterPlayer", "Teams", "TextChatService",
|
||||
"VoiceChatService", "Workspace",
|
||||
}
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
|
@ -27,8 +35,11 @@ local ignorePlaceIds = require(Plugin.ignorePlaceIds)
|
|||
local timeUtil = require(Plugin.timeUtil)
|
||||
local Theme = require(script.Theme)
|
||||
|
||||
local Http = require(Packages.Http)
|
||||
|
||||
local Page = require(script.Page)
|
||||
local Notifications = require(script.Components.Notifications)
|
||||
local TextButton = require(script.Components.TextButton)
|
||||
local Tooltip = require(script.Components.Tooltip)
|
||||
local StudioPluginAction = require(script.Components.Studio.StudioPluginAction)
|
||||
local StudioToolbar = require(script.Components.Studio.StudioToolbar)
|
||||
|
|
@ -604,6 +615,68 @@ function App:useRunningConnectionInfo()
|
|||
self.setPort(port)
|
||||
end
|
||||
|
||||
function App:performSyncback()
|
||||
self:setState({ showingSyncbackConfirm = false })
|
||||
|
||||
local host = self.host:getValue()
|
||||
local port = self.port:getValue()
|
||||
|
||||
if host == "" then
|
||||
host = Config.defaultHost
|
||||
end
|
||||
if port == "" then
|
||||
port = Config.defaultPort
|
||||
end
|
||||
|
||||
Promise.new(function(resolve, reject)
|
||||
local services = {}
|
||||
for _, className in SYNCBACK_SERVICES do
|
||||
local ok, service = pcall(game.FindService, game, className)
|
||||
if ok and service and #service:GetChildren() > 0 then
|
||||
local buf = SerializationService:SerializeInstancesAsync(service:GetChildren())
|
||||
table.insert(services, {
|
||||
className = className,
|
||||
data = buf,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local url = ("http://%s:%s/api/syncback"):format(host, port)
|
||||
local body = Http.msgpackEncode({
|
||||
protocolVersion = Config.protocolVersion,
|
||||
serverVersion = Config.expectedServerVersionString,
|
||||
placeId = game.PlaceId,
|
||||
services = services,
|
||||
})
|
||||
|
||||
Http.post(url, body)
|
||||
:andThen(function(response)
|
||||
if response.StatusCode >= 200 and response.StatusCode < 300 then
|
||||
resolve()
|
||||
else
|
||||
reject(
|
||||
`Syncback request failed ({response.StatusCode}): {response.Body or "unknown error"}`
|
||||
)
|
||||
end
|
||||
end)
|
||||
:catch(reject)
|
||||
end)
|
||||
:andThen(function()
|
||||
Log.info("Syncback data sent to server.")
|
||||
self:addNotification({
|
||||
text = "Syncback data sent. Server is processing.",
|
||||
timeout = 10,
|
||||
})
|
||||
end)
|
||||
:catch(function(err)
|
||||
Log.warn("Syncback failed: " .. tostring(err))
|
||||
self:addNotification({
|
||||
text = "Syncback failed: " .. tostring(err),
|
||||
timeout = 10,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
function App:startSession()
|
||||
-- If a session is already in progress, tear it down first.
|
||||
-- In one-shot mode the sync lock is skipped, so this is the only guard
|
||||
|
|
@ -1027,23 +1100,29 @@ function App:render()
|
|||
}, {
|
||||
Tooltips = e(Tooltip.Container, nil),
|
||||
|
||||
NotConnectedPage = createPageElement(AppStatus.NotConnected, {
|
||||
host = self.host,
|
||||
onHostChange = self.setHost,
|
||||
port = self.port,
|
||||
onPortChange = self.setPort,
|
||||
NotConnectedPage = createPageElement(AppStatus.NotConnected, {
|
||||
host = self.host,
|
||||
onHostChange = self.setHost,
|
||||
port = self.port,
|
||||
onPortChange = self.setPort,
|
||||
|
||||
onConnect = function()
|
||||
self:startSession()
|
||||
end,
|
||||
onConnect = function()
|
||||
self:startSession()
|
||||
end,
|
||||
|
||||
onNavigateSettings = function()
|
||||
self.backPage = AppStatus.NotConnected
|
||||
self:setState({
|
||||
appStatus = AppStatus.Settings,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
onSyncback = function()
|
||||
self:setState({
|
||||
showingSyncbackConfirm = true,
|
||||
})
|
||||
end,
|
||||
|
||||
onNavigateSettings = function()
|
||||
self.backPage = AppStatus.NotConnected
|
||||
self:setState({
|
||||
appStatus = AppStatus.Settings,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
|
||||
ConfirmingPage = createPageElement(AppStatus.Confirming, {
|
||||
confirmData = self.state.confirmData,
|
||||
|
|
@ -1117,22 +1196,100 @@ function App:render()
|
|||
}),
|
||||
}),
|
||||
|
||||
RojoNotifications = e("ScreenGui", {
|
||||
ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
ResetOnSpawn = false,
|
||||
DisplayOrder = 100,
|
||||
}, {
|
||||
Notifications = e(Notifications, {
|
||||
soundPlayer = self.props.soundPlayer,
|
||||
notifications = self.state.notifications,
|
||||
onClose = function(id)
|
||||
self:closeNotification(id)
|
||||
end,
|
||||
}),
|
||||
RojoNotifications = e("ScreenGui", {
|
||||
ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
ResetOnSpawn = false,
|
||||
DisplayOrder = 100,
|
||||
}, {
|
||||
Notifications = e(Notifications, {
|
||||
soundPlayer = self.props.soundPlayer,
|
||||
notifications = self.state.notifications,
|
||||
onClose = function(id)
|
||||
self:closeNotification(id)
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
toggleAction = e(StudioPluginAction, {
|
||||
SyncbackConfirm = e(StudioPluginGui, {
|
||||
id = "Atlas_SyncbackConfirm",
|
||||
title = "Full Syncback",
|
||||
active = self.state.showingSyncbackConfirm == true,
|
||||
isEphemeral = true,
|
||||
|
||||
initDockState = Enum.InitialDockState.Float,
|
||||
overridePreviousState = true,
|
||||
floatingSize = Vector2.new(400, 150),
|
||||
minimumSize = Vector2.new(300, 120),
|
||||
|
||||
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
|
||||
onClose = function()
|
||||
self:setState({ showingSyncbackConfirm = false })
|
||||
end,
|
||||
}, {
|
||||
Content = Theme.with(function(theme)
|
||||
return e("Frame", {
|
||||
Size = UDim2.fromScale(1, 1),
|
||||
BackgroundColor3 = theme.BackgroundColor,
|
||||
BorderSizePixel = 0,
|
||||
}, {
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 16),
|
||||
PaddingRight = UDim.new(0, 16),
|
||||
PaddingTop = UDim.new(0, 16),
|
||||
PaddingBottom = UDim.new(0, 16),
|
||||
}),
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 16),
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
}),
|
||||
Message = e("TextLabel", {
|
||||
Text = "This will overwrite your project files with the current Studio state. This cannot be undone.",
|
||||
TextWrapped = true,
|
||||
FontFace = theme.Font.Main,
|
||||
TextSize = theme.TextSize.Body,
|
||||
TextColor3 = theme.TextColor,
|
||||
Size = UDim2.new(1, 0, 0, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
}),
|
||||
Buttons = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 34),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2,
|
||||
}, {
|
||||
Layout = e("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
Cancel = e(TextButton, {
|
||||
text = "Cancel",
|
||||
style = "Bordered",
|
||||
layoutOrder = 1,
|
||||
onClick = function()
|
||||
self:setState({ showingSyncbackConfirm = false })
|
||||
end,
|
||||
}),
|
||||
Confirm = e(TextButton, {
|
||||
text = "Syncback",
|
||||
style = "Danger",
|
||||
layoutOrder = 2,
|
||||
onClick = function()
|
||||
self:performSyncback()
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end),
|
||||
}),
|
||||
|
||||
toggleAction = e(StudioPluginAction, {
|
||||
name = "AtlasConnection",
|
||||
title = "Atlas: Connect/Disconnect",
|
||||
description = "Toggles the server for an Atlas sync session",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue