Play Solo & Test Server auto connect (#840)

When enabled, the `baseurl` of the session is written to
`workspace:SetAttribute("__Rojo_ConnectionUrl")` so that the test server
can connect to that session automatically.

This works for Play Solo and Local Test Server. It is marked
experimental for now (and disabled by default) since connecting during a
playtest session is... not polished. Rojo may overwrite things and cause
headaches. Further work can be done later.
This commit is contained in:
boatbomber
2024-01-30 12:51:45 -08:00
committed by GitHub
parent 4018607b77
commit 506a60d0be
4 changed files with 99 additions and 15 deletions

View File

@@ -3,6 +3,7 @@
## Unreleased Changes ## Unreleased Changes
* Added popout diff visualizer for table properties like Attributes and Tags ([#834]) * Added popout diff visualizer for table properties like Attributes and Tags ([#834])
* Updated Theme to use Studio colors ([#838]) * Updated Theme to use Studio colors ([#838])
* Added experimental setting for Auto Connect in playtests ([#840])
* Projects may now specify rules for syncing files as if they had a different file extension. ([#813]) * Projects may now specify rules for syncing files as if they had a different file extension. ([#813])
This is specified via a new field on project files, `syncRules`: This is specified via a new field on project files, `syncRules`:
@@ -53,6 +54,7 @@
[#813]: https://github.com/rojo-rbx/rojo/pull/813 [#813]: https://github.com/rojo-rbx/rojo/pull/813
[#834]: https://github.com/rojo-rbx/rojo/pull/834 [#834]: https://github.com/rojo-rbx/rojo/pull/834
[#838]: https://github.com/rojo-rbx/rojo/pull/838 [#838]: https://github.com/rojo-rbx/rojo/pull/838
[#840]: https://github.com/rojo-rbx/rojo/pull/840
## [7.4.0] - January 16, 2024 ## [7.4.0] - January 16, 2024
* Improved the visualization for array properties like Tags ([#829]) * Improved the visualization for array properties like Tags ([#829])

View File

@@ -75,6 +75,12 @@ function SettingsPage:init()
end end
function SettingsPage:render() function SettingsPage:render()
local layoutOrder = 0
local function layoutIncrement()
layoutOrder += 1
return layoutOrder
end
return Theme.with(function(theme) return Theme.with(function(theme)
theme = theme.Settings theme = theme.Settings
@@ -86,7 +92,7 @@ function SettingsPage:render()
Navbar = e(Navbar, { Navbar = e(Navbar, {
onBack = self.props.onBack, onBack = self.props.onBack,
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 0, layoutOrder = layoutIncrement(),
}), }),
ShowNotifications = e(Setting, { ShowNotifications = e(Setting, {
@@ -94,7 +100,7 @@ function SettingsPage:render()
name = "Show Notifications", name = "Show Notifications",
description = "Popup notifications in viewport", description = "Popup notifications in viewport",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 1, layoutOrder = layoutIncrement(),
}), }),
SyncReminder = e(Setting, { SyncReminder = e(Setting, {
@@ -103,7 +109,7 @@ function SettingsPage:render()
description = "Notify to sync when opening a place that has previously been synced", description = "Notify to sync when opening a place that has previously been synced",
transparency = self.props.transparency, transparency = self.props.transparency,
visible = Settings:getBinding("showNotifications"), visible = Settings:getBinding("showNotifications"),
layoutOrder = 2, layoutOrder = layoutIncrement(),
}), }),
ConfirmationBehavior = e(Setting, { ConfirmationBehavior = e(Setting, {
@@ -111,7 +117,7 @@ function SettingsPage:render()
name = "Confirmation Behavior", name = "Confirmation Behavior",
description = "When to prompt for confirmation before syncing", description = "When to prompt for confirmation before syncing",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 3, layoutOrder = layoutIncrement(),
options = confirmationBehaviors, options = confirmationBehaviors,
}), }),
@@ -121,7 +127,7 @@ function SettingsPage:render()
name = "Confirmation Threshold", name = "Confirmation Threshold",
description = "How many modified instances to be considered a large change", description = "How many modified instances to be considered a large change",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 4, layoutOrder = layoutIncrement(),
visible = Settings:getBinding("confirmationBehavior"):map(function(value) visible = Settings:getBinding("confirmationBehavior"):map(function(value)
return value == "Large Changes" return value == "Large Changes"
end), end),
@@ -152,7 +158,16 @@ function SettingsPage:render()
name = "Play Sounds", name = "Play Sounds",
description = "Toggle sound effects", description = "Toggle sound effects",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 5, layoutOrder = layoutIncrement(),
}),
AutoConnectPlaytestServer = e(Setting, {
id = "autoConnectPlaytestServer",
name = "Auto Connect Playtest Server",
description = "Automatically connect game server to Rojo when playtesting while connected in Edit",
experimental = true,
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
}), }),
OpenScriptsExternally = e(Setting, { OpenScriptsExternally = e(Setting, {
@@ -162,7 +177,7 @@ function SettingsPage:render()
locked = self.props.syncActive, locked = self.props.syncActive,
experimental = true, experimental = true,
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 6, layoutOrder = layoutIncrement(),
}), }),
TwoWaySync = e(Setting, { TwoWaySync = e(Setting, {
@@ -172,7 +187,7 @@ function SettingsPage:render()
locked = self.props.syncActive, locked = self.props.syncActive,
experimental = true, experimental = true,
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 7, layoutOrder = layoutIncrement(),
}), }),
LogLevel = e(Setting, { LogLevel = e(Setting, {
@@ -180,7 +195,7 @@ function SettingsPage:render()
name = "Log Level", name = "Log Level",
description = "Plugin output verbosity level", description = "Plugin output verbosity level",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 100, layoutOrder = layoutIncrement(),
options = invertedLevels, options = invertedLevels,
showReset = Settings:getBinding("logLevel"):map(function(value) showReset = Settings:getBinding("logLevel"):map(function(value)
@@ -196,7 +211,7 @@ function SettingsPage:render()
name = "Typechecking", name = "Typechecking",
description = "Toggle typechecking on the API surface", description = "Toggle typechecking on the API surface",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 101, layoutOrder = layoutIncrement(),
}), }),
Layout = e("UIListLayout", { Layout = e("UIListLayout", {

View File

@@ -158,11 +158,29 @@ function App:init()
}, },
}) })
end end
if self:isAutoConnectPlaytestServerAvailable() then
self:useRunningConnectionInfo()
self:startSession()
end
self.autoConnectPlaytestServerListener = Settings:onChanged("autoConnectPlaytestServer", function(enabled)
if enabled then
if self:isAutoConnectPlaytestServerWriteable() and self.serveSession ~= nil then
-- Write the existing session
local baseUrl = self.serveSession.__apiContext.__baseUrl
self:setRunningConnectionInfo(baseUrl)
end
else
self:clearRunningConnectionInfo()
end
end)
end end
function App:willUnmount() function App:willUnmount()
self.waypointConnection:Disconnect() self.waypointConnection:Disconnect()
self.confirmationBindable:Destroy() self.confirmationBindable:Destroy()
self.autoConnectPlaytestServerListener()
self:clearRunningConnectionInfo()
end end
function App:addNotification( function App:addNotification(
@@ -278,10 +296,7 @@ function App:getHostAndPort()
local host = self.host:getValue() local host = self.host:getValue()
local port = self.port:getValue() local port = self.port:getValue()
local host = if #host > 0 then host else Config.defaultHost return if #host > 0 then host else Config.defaultHost, if #port > 0 then port else Config.defaultPort
local port = if #port > 0 then port else Config.defaultPort
return host, port
end end
function App:isSyncLockAvailable() function App:isSyncLockAvailable()
@@ -349,6 +364,49 @@ function App:releaseSyncLock()
Log.trace("Could not relase sync lock because it is owned by {}", lock.Value) Log.trace("Could not relase sync lock because it is owned by {}", lock.Value)
end end
function App:isAutoConnectPlaytestServerAvailable()
return RunService:IsRunMode()
and RunService:IsServer()
and Settings:get("autoConnectPlaytestServer")
and workspace:GetAttribute("__Rojo_ConnectionUrl")
end
function App:isAutoConnectPlaytestServerWriteable()
return RunService:IsEdit() and Settings:get("autoConnectPlaytestServer")
end
function App:setRunningConnectionInfo(baseUrl: string)
if not self:isAutoConnectPlaytestServerWriteable() then
return
end
Log.trace("Setting connection info for play solo auto-connect")
workspace:SetAttribute("__Rojo_ConnectionUrl", baseUrl)
end
function App:clearRunningConnectionInfo()
if not RunService:IsEdit() then
-- Only write connection info from edit mode
return
end
Log.trace("Clearing connection info for play solo auto-connect")
workspace:SetAttribute("__Rojo_ConnectionUrl", nil)
end
function App:useRunningConnectionInfo()
local connectionInfo = workspace:GetAttribute("__Rojo_ConnectionUrl")
if not connectionInfo then
return
end
Log.trace("Using connection info for play solo auto-connect")
local host, port = string.match(connectionInfo, "^(.+):(.-)$")
self.setHost(host)
self.setPort(port)
end
function App:startSession() function App:startSession()
local claimedLock, priorOwner = self:claimSyncLock() local claimedLock, priorOwner = self:claimSyncLock()
if not claimedLock then if not claimedLock then
@@ -441,6 +499,7 @@ function App:startSession()
self:addNotification("Connecting to session...") self:addNotification("Connecting to session...")
elseif status == ServeSession.Status.Connected then elseif status == ServeSession.Status.Connected then
self.knownProjects[details] = true self.knownProjects[details] = true
self:setRunningConnectionInfo(baseUrl)
local address = ("%s:%s"):format(host, port) local address = ("%s:%s"):format(host, port)
self:setState({ self:setState({
@@ -453,6 +512,7 @@ function App:startSession()
elseif status == ServeSession.Status.Disconnected then elseif status == ServeSession.Status.Disconnected then
self.serveSession = nil self.serveSession = nil
self:releaseSyncLock() self:releaseSyncLock()
self:clearRunningConnectionInfo()
self:setState({ self:setState({
patchData = { patchData = {
patch = PatchSet.newEmpty(), patch = PatchSet.newEmpty(),
@@ -488,6 +548,12 @@ function App:startSession()
return "Accept" return "Accept"
end end
-- Play solo auto-connect does not require confirmation
if self:isAutoConnectPlaytestServerAvailable() then
Log.trace("Accepting patch without confirmation because play solo auto-connect is enabled")
return "Accept"
end
local confirmationBehavior = Settings:get("confirmationBehavior") local confirmationBehavior = Settings:get("confirmationBehavior")
if confirmationBehavior == "Initial" then if confirmationBehavior == "Initial" then
-- Only confirm if we haven't synced this project yet this session -- Only confirm if we haven't synced this project yet this session
@@ -603,7 +669,7 @@ function App:render()
value = self.props.plugin, value = self.props.plugin,
}, { }, {
e(Theme.StudioProvider, nil, { e(Theme.StudioProvider, nil, {
e(Tooltip.Provider, nil, { tooltip = e(Tooltip.Provider, nil, {
gui = e(StudioPluginGui, { gui = e(StudioPluginGui, {
id = pluginName, id = pluginName,
title = pluginName, title = pluginName,

View File

@@ -14,6 +14,7 @@ local defaultSettings = {
twoWaySync = false, twoWaySync = false,
showNotifications = true, showNotifications = true,
syncReminder = true, syncReminder = true,
autoConnectPlaytestServer = false,
confirmationBehavior = "Initial", confirmationBehavior = "Initial",
largeChangesConfirmationThreshold = 5, largeChangesConfirmationThreshold = 5,
playSounds = true, playSounds = true,