Compare commits

..

21 Commits

Author SHA1 Message Date
Lucien Greathouse
88d2d1f193 Stub out a command sorta like rostar unpack 2022-06-10 03:15:44 -04:00
Lucien Greathouse
2e7c4b6dff Update Changelog 2022-06-10 02:15:07 -04:00
Lucien Greathouse
e5dbee1073 Defer application of init.meta.json until after init Lua files. (#549)
Fixes #546.
2022-06-09 21:42:37 -04:00
Lucien Greathouse
c06463b61d Update CHANGELOG 2022-06-05 17:48:17 -04:00
Lucien Greathouse
10341e3776 Major Performance Improvements (#548)
* Use WeakDom::into_raw for faster snapshot generation from models

* Make compute_patch_set take snapshots by value

* Stop deferring property application in apply_patch_set

* Use InstanceBuilder::empty to avoid extra name allocations

* Git dependencies, skip dropping ServeSession

* Use std::mem::forget instead of ManuallyDrop

* Switch to latest rbx-dom crates.io dependencies

* Update other dependencies
2022-06-05 17:47:31 -04:00
Lucien Greathouse
824cdc5dcd Annotate snapshot_rbxm for profiling 2022-05-27 18:13:07 -04:00
Lucien Greathouse
7aa7a35aa5 Add profiling info and optional profiling with Tracy 2022-05-27 03:08:54 -04:00
Lucien Greathouse
79b57b3359 Move memofs and rojo-insta-ext into crates folder 2022-05-26 04:23:44 -04:00
Lucien Greathouse
c7aeffe586 Switch from structopt to clap 2022-05-26 04:19:51 -04:00
Lucien Greathouse
79c02f2457 Delete old bin folder and update foreman.toml 2022-05-26 04:13:50 -04:00
Lucien Greathouse
b9ed68fa9e Release v7.1.1 2022-05-26 02:53:20 -04:00
Lucien Greathouse
6c6d6c9c8d Add .github/FUNDING.yml 2022-05-26 02:28:57 -04:00
Lucien Greathouse
e169d7be68 New release workflow (#547)
* Port release workflow from Aftman to test

* Checkout submodules in plugin build step

* ...and build with submodules for other builds too

* Fix ci.yml; we use master branch still

* CI with submodules too
2022-05-25 22:26:22 -04:00
Lucien Greathouse
192fd7d4dd New and improved CI pipeline 2022-05-25 18:53:08 -04:00
Lucien Greathouse
1f1193e857 Remove unused lazy_static 2022-05-25 18:48:57 -04:00
boatbomber
0a412ade88 Remove duplicate PluginSettings.StudioProvider (#545) 2022-05-25 18:48:10 -04:00
Filip Tibell
3cef2fe9aa Fix sourcemap command not stripping paths correctly (#544)
* Fix sourcemap command not stripping paths correctly

* Use ServeSession to get the proper root dir to strip for sourcemap
2022-05-23 15:19:30 -04:00
Lucien Greathouse
18e53f06fe Remove unused dependencies and dead code warnings 2022-05-22 19:20:41 -04:00
Lucien Greathouse
eaac539087 Update to reqwest 0.11.10 2022-05-22 19:16:43 -04:00
Lucien Greathouse
57005c4fd5 Update uuid and winreg 2022-05-22 19:13:11 -04:00
Lucien Greathouse
ea58999a2a Update to pretty_assertions 1.2.1 2022-05-22 19:12:33 -04:00
64 changed files with 1055 additions and 1367 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
patreon: lpghatguy

View File

@@ -11,29 +11,49 @@ on:
jobs: jobs:
build: build:
name: Build and Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
rust_version: [stable, "1.55.0"] rust_version: [stable, 1.55.0]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- name: Setup Rust toolchain - name: Install Rust
run: rustup default ${{ matrix.rust_version }} uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust_version }}
override: true
profile: minimal
- name: Build - name: Build
run: cargo build --locked --verbose run: cargo build --locked --verbose
- name: Run tests - name: Test
run: cargo test --locked --verbose run: cargo test --locked --verbose
- name: Rustfmt and Clippy lint:
run: | name: Rustfmt and Clippy
cargo fmt -- --check runs-on: ubuntu-latest
cargo clippy
if: matrix.rust_version == 'stable' steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
components: rustfmt, clippy
- name: Rustfmt
run: cargo fmt -- --check
- name: Clippy
run: cargo clippy

View File

@@ -2,65 +2,152 @@ name: Release
on: on:
push: push:
tags: ["*"] tags: ["v*"]
jobs: jobs:
windows: create-release:
runs-on: windows-latest name: Create Release
steps:
- uses: actions/checkout@v1
with:
submodules: true
- name: Build release binary
run: cargo build --verbose --locked --release
- name: Upload artifacts
uses: actions/upload-artifact@v1
with:
name: rojo-win64
path: target/release/rojo.exe
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v1
with:
submodules: true
- name: Install Rust
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
- name: Build release binary
run: |
source $HOME/.cargo/env
cargo build --verbose --locked --release
env:
OPENSSL_STATIC: 1
- name: Upload artifacts
uses: actions/upload-artifact@v1
with:
name: rojo-macos
path: target/release/rojo
linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps: steps:
- uses: actions/checkout@v1 - name: Create Release
with: id: create_release
submodules: true uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: false
- name: Build build-plugin:
run: cargo build --locked --verbose --release needs: ["create-release"]
env: name: Build Roblox Studio Plugin
OPENSSL_STATIC: 1 runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Upload artifacts - name: Setup Foreman
uses: actions/upload-artifact@v1 uses: Roblox/setup-foreman@v1
with: with:
name: rojo-linux token: ${{ secrets.GITHUB_TOKEN }}
path: target/release/rojo
- name: Build Plugin
run: rojo build plugin --output Rojo.rbxm
- name: Upload Plugin to Release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: Rojo.rbxm
asset_name: Rojo.rbxm
asset_content_type: application/octet-stream
- name: Upload Plugin to Artifacts
uses: actions/upload-artifact@v3
with:
name: Rojo.rbxm
path: Rojo.rbxm
build:
needs: ["create-release"]
strategy:
fail-fast: false
matrix:
# https://doc.rust-lang.org/rustc/platform-support.html
#
# FIXME: After the Rojo VS Code extension updates, add architecture
# names to each of these releases. We'll rename win64 to windows and add
# -x86_64 to each release.
include:
- host: linux
os: ubuntu-latest
target: x86_64-unknown-linux-gnu
label: linux
- host: windows
os: windows-latest
target: x86_64-pc-windows-msvc
label: win64
- host: macos
os: macos-latest
target: x86_64-apple-darwin
label: macos
- host: macos
os: macos-latest
target: aarch64-apple-darwin
label: macos-aarch64
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
env:
BIN: rojo
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Get Version from Tag
shell: bash
# https://github.community/t/how-to-get-just-the-tag-name/16241/7#M1027
run: |
echo "PROJECT_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
echo "Version is: ${{ env.PROJECT_VERSION }}"
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
override: true
profile: minimal
- name: Build Release
run: cargo build --release --locked --verbose
env:
# Build into a known directory so we can find our build artifact more
# easily.
CARGO_TARGET_DIR: output
# On platforms that use OpenSSL, ensure it is statically linked to
# make binaries more portable.
OPENSSL_STATIC: 1
- name: Create Release Archive
shell: bash
run: |
mkdir staging
if [ "${{ matrix.host }}" = "windows" ]; then
cp "output/release/$BIN.exe" staging/
cd staging
7z a ../release.zip *
else
cp "output/release/$BIN" staging/
cd staging
zip ../release.zip *
fi
- name: Upload Archive to Release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: release.zip
asset_name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
asset_content_type: application/octet-stream
- name: Upload Archive to Artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
path: release.zip

View File

@@ -1,6 +1,20 @@
# Rojo Changelog # Rojo Changelog
## Unreleased Changes ## Unreleased Changes
* Switched from structopt to clap for command line argument parsing.
* Significantly improved performance of building and serving. ([#548])
* Fixed `init.meta.json` when used with `init.lua` and related files. ([#549])
[#548]: https://github.com/rojo-rbx/rojo/pull/548
[#549]: https://github.com/rojo-rbx/rojo/pull/549
## [7.1.1] - May 26, 2022
* Fixed sourcemap command not stripping paths correctly ([#544])
* Fixed Studio plugin settings not saving correctly.
[#544]: https://github.com/rojo-rbx/rojo/pull/544
[#545]: https://github.com/rojo-rbx/rojo/pull/545
[7.1.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.1.1
## [7.1.0] - May 22, 2022 ## [7.1.0] - May 22, 2022
* Added support for specifying an address to be used by default in project files. ([#507]) * Added support for specifying an address to be used by default in project files. ([#507])

View File

@@ -49,11 +49,9 @@ The Rojo release process is pretty manual right now. If you need to do it, here'
* `cargo publish` * `cargo publish`
8. Publish the Plugin 8. Publish the Plugin
* `cargo run -- upload plugin --asset_id 6415005344` * `cargo run -- upload plugin --asset_id 6415005344`
* `cargo run -- build plugin --output Rojo.rbxm`
9. Push commits and tags 9. Push commits and tags
* `git push && git push --tags` * `git push && git push --tags`
10. Copy GitHub release content from previous release 10. Copy GitHub release content from previous release
* Update the leading text with a summary about the release * Update the leading text with a summary about the release
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md) * Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
* Write a small summary of each major feature * Write a small summary of each major feature
* Attach release artifacts from GitHub Actions for each platform

1315
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "rojo" name = "rojo"
version = "7.1.0" version = "7.1.1"
authors = ["Lucien Greathouse <me@lpghatguy.com>"] authors = ["Lucien Greathouse <me@lpghatguy.com>"]
description = "Enables professional-grade development tools for Roblox developers" description = "Enables professional-grade development tools for Roblox developers"
license = "MPL-2.0" license = "MPL-2.0"
@@ -27,11 +27,10 @@ default = []
# Enable this feature to live-reload assets from the web UI. # Enable this feature to live-reload assets from the web UI.
dev_live_assets = [] dev_live_assets = []
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client"]
[workspace] [workspace]
members = [ members = ["crates/*"]
"rojo-insta-ext",
"memofs",
]
[lib] [lib]
name = "librojo" name = "librojo"
@@ -42,7 +41,7 @@ name = "build"
harness = false harness = false
[dependencies] [dependencies]
memofs = { version = "0.2.0", path = "memofs" } memofs = { version = "0.2.0", path = "crates/memofs" }
# These dependencies can be uncommented when working on rbx-dom simultaneously # These dependencies can be uncommented when working on rbx-dom simultaneously
# rbx_binary = { path = "../rbx-dom/rbx_binary" } # rbx_binary = { path = "../rbx-dom/rbx_binary" }
@@ -52,7 +51,7 @@ memofs = { version = "0.2.0", path = "memofs" }
# rbx_xml = { path = "../rbx-dom/rbx_xml" } # rbx_xml = { path = "../rbx-dom/rbx_xml" }
rbx_binary = "0.6.4" rbx_binary = "0.6.4"
rbx_dom_weak = "2.3.0" rbx_dom_weak = "2.4.0"
rbx_reflection = "4.2.0" rbx_reflection = "4.2.0"
rbx_reflection_database = "0.2.2" rbx_reflection_database = "0.2.2"
rbx_xml = "0.12.3" rbx_xml = "0.12.3"
@@ -69,29 +68,28 @@ globset = "0.4.8"
humantime = "2.1.0" humantime = "2.1.0"
hyper = { version = "0.14.13", features = ["server", "tcp", "http1"] } hyper = { version = "0.14.13", features = ["server", "tcp", "http1"] }
jod-thread = "0.1.2" jod-thread = "0.1.2"
lazy_static = "1.4.0"
log = "0.4.14" log = "0.4.14"
maplit = "1.0.2" maplit = "1.0.2"
notify = "4.0.17" notify = "4.0.17"
opener = "0.5.0" opener = "0.5.0"
regex = "1.5.4" reqwest = { version = "0.11.10", features = ["blocking", "json"] }
reqwest = "0.9.24"
ritz = "0.1.0" ritz = "0.1.0"
rlua = "0.17.1"
roblox_install = "1.0.0" roblox_install = "1.0.0"
serde = { version = "1.0.130", features = ["derive", "rc"] } serde = { version = "1.0.130", features = ["derive", "rc"] }
serde_json = "1.0.68" serde_json = "1.0.68"
structopt = "0.3.23"
termcolor = "1.1.2" termcolor = "1.1.2"
thiserror = "1.0.30" thiserror = "1.0.30"
tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] } tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
uuid = { version = "0.8.2", features = ["v4", "serde"] } uuid = { version = "1.0.0", features = ["v4", "serde"] }
clap = { version = "3.1.18", features = ["derive"] }
profiling = "1.0.6"
tracy-client = { version = "0.13.2", optional = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = "0.9.0" winreg = "0.10.1"
[build-dependencies] [build-dependencies]
memofs = { version = "0.2.0", path = "memofs" } memofs = { version = "0.2.0", path = "crates/memofs" }
embed-resource = "1.6.4" embed-resource = "1.6.4"
anyhow = "1.0.44" anyhow = "1.0.44"
@@ -100,13 +98,12 @@ fs-err = "2.6.0"
maplit = "1.0.2" maplit = "1.0.2"
[dev-dependencies] [dev-dependencies]
rojo-insta-ext = { path = "rojo-insta-ext" } rojo-insta-ext = { path = "crates/rojo-insta-ext" }
criterion = "0.3.5" criterion = "0.3.5"
insta = { version = "1.8.0", features = ["redactions"] } insta = { version = "1.8.0", features = ["redactions"] }
lazy_static = "1.4.0"
paste = "1.0.5" paste = "1.0.5"
pretty_assertions = "0.7.2" pretty_assertions = "1.2.1"
serde_yaml = "0.8.21" serde_yaml = "0.8.21"
tempfile = "3.2.0" tempfile = "3.2.0"
walkdir = "2.3.2" walkdir = "2.3.2"

View File

@@ -1,5 +0,0 @@
#!/bin/sh
set -e
watchexec -c -w plugin "sh -c './bin/install-dev-plugin.sh'"

View File

@@ -1,13 +0,0 @@
#!/bin/sh
set -e
DIR="$( mktemp -d )"
PLUGIN_FILE="$DIR/Rojo.rbxm"
TESTEZ_FILE="$DIR/TestEZ.rbxm"
rojo build plugin -o "$PLUGIN_FILE"
rojo build plugin/testez.project.json -o "$TESTEZ_FILE"
remodel bin/mark-plugin-as-dev.lua "$PLUGIN_FILE" "$TESTEZ_FILE" 2>/dev/null
cp "$PLUGIN_FILE" "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"

View File

@@ -1,5 +0,0 @@
#!/bin/sh
set -e
rojo build plugin -o "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"

View File

@@ -1,12 +0,0 @@
local pluginPath, testezPath = ...
local plugin = remodel.readModelFile(pluginPath)[1]
local testez = remodel.readModelFile(testezPath)[1]
local marker = Instance.new("Folder")
marker.Name = "ROJO_DEV_BUILD"
marker.Parent = plugin
testez.Parent = plugin
remodel.writeModelFile(plugin, pluginPath)

View File

@@ -1,8 +0,0 @@
local pluginPath, placePath = ...
local plugin = remodel.readModelFile(pluginPath)[1]
local place = remodel.readPlaceFile(placePath)
plugin.Parent = place:GetService("ReplicatedStorage")
remodel.writePlaceFile(place, placePath)

View File

@@ -1,6 +0,0 @@
#!/bin/sh
set -e
./bin/run-cli-tests.sh
./bin/run-plugin-tests.sh

View File

@@ -1,9 +0,0 @@
#!/bin/sh
set -e
cargo test --all --locked
cargo fmt -- --check
touch src/lib.rs # Nudge Rust source to make Clippy actually check things
cargo clippy

View File

@@ -1,16 +0,0 @@
#!/bin/sh
set -e
DIR="$( mktemp -d )"
PLUGIN_FILE="$DIR/Rojo.rbxmx"
PLACE_FILE="$DIR/RojoTestPlace.rbxlx"
rojo build plugin -o "$PLUGIN_FILE"
rojo build plugin/place.project.json -o "$PLACE_FILE"
remodel bin/put-plugin-in-test-place.lua "$PLUGIN_FILE" "$PLACE_FILE"
run-in-roblox -s plugin/testBootstrap.server.lua "$PLACE_FILE"
luacheck plugin/src plugin/log plugin/http

View File

@@ -1,3 +1,4 @@
[tools] [tools]
rojo = { source = "rojo-rbx/rojo", version = "6.1.0" } rojo = { source = "rojo-rbx/rojo", version = "7.1.1" }
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" } run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
selene = { source = "Kampfkarren/selene", version = "0.17.0" }

View File

@@ -154,150 +154,146 @@ function App:render()
value = self.props.plugin, value = self.props.plugin,
}, { }, {
e(Theme.StudioProvider, nil, { e(Theme.StudioProvider, nil, {
e(PluginSettings.StudioProvider, { gui = e(StudioPluginGui, {
plugin = self.props.plugin, id = pluginName,
title = pluginName,
active = self.state.guiEnabled,
initDockState = Enum.InitialDockState.Right,
initEnabled = false,
overridePreviousState = false,
floatingSize = Vector2.new(300, 200),
minimumSize = Vector2.new(300, 200),
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
onInitialState = function(initialState)
self:setState({
guiEnabled = initialState,
})
end,
onClose = function()
self:setState({
guiEnabled = false,
})
end,
}, { }, {
gui = e(StudioPluginGui, { NotConnectedPage = createPageElement(AppStatus.NotConnected, {
id = pluginName, host = self.host,
title = pluginName, onHostChange = self.setHost,
active = self.state.guiEnabled, port = self.port,
onPortChange = self.setPort,
initDockState = Enum.InitialDockState.Right, onConnect = function()
initEnabled = false, self:startSession()
overridePreviousState = false, end,
floatingSize = Vector2.new(300, 200),
minimumSize = Vector2.new(300, 200),
zIndexBehavior = Enum.ZIndexBehavior.Sibling, onNavigateSettings = function()
onInitialState = function(initialState)
self:setState({ self:setState({
guiEnabled = initialState, appStatus = AppStatus.Settings,
}) })
end, end,
}),
Connecting = createPageElement(AppStatus.Connecting),
Connected = createPageElement(AppStatus.Connected, {
projectName = self.state.projectName,
address = self.state.address,
onDisconnect = function()
self:endSession()
end,
}),
Settings = createPageElement(AppStatus.Settings, {
onBack = function()
self:setState({
appStatus = AppStatus.NotConnected,
})
end,
}),
Error = createPageElement(AppStatus.Error, {
errorMessage = self.state.errorMessage,
onClose = function() onClose = function()
self:setState({ self:setState({
guiEnabled = false, appStatus = AppStatus.NotConnected,
toolbarIcon = Assets.Images.PluginButton,
}) })
end, end,
}, {
NotConnectedPage = createPageElement(AppStatus.NotConnected, {
host = self.host,
onHostChange = self.setHost,
port = self.port,
onPortChange = self.setPort,
onConnect = function()
self:startSession()
end,
onNavigateSettings = function()
self:setState({
appStatus = AppStatus.Settings,
})
end,
}),
Connecting = createPageElement(AppStatus.Connecting),
Connected = createPageElement(AppStatus.Connected, {
projectName = self.state.projectName,
address = self.state.address,
onDisconnect = function()
self:endSession()
end,
}),
Settings = createPageElement(AppStatus.Settings, {
onBack = function()
self:setState({
appStatus = AppStatus.NotConnected,
})
end,
}),
Error = createPageElement(AppStatus.Error, {
errorMessage = self.state.errorMessage,
onClose = function()
self:setState({
appStatus = AppStatus.NotConnected,
toolbarIcon = Assets.Images.PluginButton,
})
end,
}),
Background = Theme.with(function(theme)
return e("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundColor3 = theme.BackgroundColor,
ZIndex = 0,
BorderSizePixel = 0,
})
end),
}), }),
toggleAction = e(StudioPluginAction, { Background = Theme.with(function(theme)
name = "RojoConnection", return e("Frame", {
title = "Rojo: Connect/Disconnect", Size = UDim2.new(1, 0, 1, 0),
description = "Toggles the server for a Rojo sync session", BackgroundColor3 = theme.BackgroundColor,
icon = Assets.Images.PluginButton, ZIndex = 0,
bindable = true, BorderSizePixel = 0,
onTriggered = function()
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
self:startSession()
elseif self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
self:endSession()
end
end,
}),
connectAction = e(StudioPluginAction, {
name = "RojoConnect",
title = "Rojo: Connect",
description = "Connects the server for a Rojo sync session",
icon = Assets.Images.PluginButton,
bindable = true,
onTriggered = function()
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
self:startSession()
end
end,
}),
disconnectAction = e(StudioPluginAction, {
name = "RojoDisconnect",
title = "Rojo: Disconnect",
description = "Disconnects the server for a Rojo sync session",
icon = Assets.Images.PluginButton,
bindable = true,
onTriggered = function()
if self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
self:endSession()
end
end,
}),
toolbar = e(StudioToolbar, {
name = pluginName,
}, {
button = e(StudioToggleButton, {
name = "Rojo",
tooltip = "Show or hide the Rojo panel",
icon = self.state.toolbarIcon,
active = self.state.guiEnabled,
enabled = true,
onClick = function()
self:setState(function(state)
return {
guiEnabled = not state.guiEnabled,
}
end)
end,
}) })
}), end),
}),
toggleAction = e(StudioPluginAction, {
name = "RojoConnection",
title = "Rojo: Connect/Disconnect",
description = "Toggles the server for a Rojo sync session",
icon = Assets.Images.PluginButton,
bindable = true,
onTriggered = function()
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
self:startSession()
elseif self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
self:endSession()
end
end,
}),
connectAction = e(StudioPluginAction, {
name = "RojoConnect",
title = "Rojo: Connect",
description = "Connects the server for a Rojo sync session",
icon = Assets.Images.PluginButton,
bindable = true,
onTriggered = function()
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
self:startSession()
end
end,
}),
disconnectAction = e(StudioPluginAction, {
name = "RojoDisconnect",
title = "Rojo: Disconnect",
description = "Disconnects the server for a Rojo sync session",
icon = Assets.Images.PluginButton,
bindable = true,
onTriggered = function()
if self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
self:endSession()
end
end,
}),
toolbar = e(StudioToolbar, {
name = pluginName,
}, {
button = e(StudioToggleButton, {
name = "Rojo",
tooltip = "Show or hide the Rojo panel",
icon = self.state.toolbarIcon,
active = self.state.guiEnabled,
enabled = true,
onClick = function()
self:setState(function(state)
return {
guiEnabled = not state.guiEnabled,
}
end)
end,
})
}), }),
}), }),
}) })
@@ -314,4 +310,4 @@ return function(props)
return e(App, settingsProps) return e(App, settingsProps)
end), end),
}) })
end end

View File

@@ -5,7 +5,7 @@ local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
return strict("Config", { return strict("Config", {
isDevBuild = isDevBuild, isDevBuild = isDevBuild,
codename = "Epiphany", codename = "Epiphany",
version = {7, 1, 0}, version = {7, 1, 1},
expectedServerVersionString = "7.0 or newer", expectedServerVersionString = "7.0 or newer",
protocolVersion = 4, protocolVersion = 4,
defaultHost = "localhost", defaultHost = "localhost",

View File

@@ -0,0 +1,14 @@
---
source: tests/tests/build.rs
assertion_line: 98
expression: contents
---
<roblox version="4">
<Item class="LocalScript" referent="0">
<Properties>
<string name="Name">issue_546</string>
<bool name="Disabled">true</bool>
<string name="Source">print("Hello, world!")</string>
</Properties>
</Item>
</roblox>

View File

@@ -0,0 +1,2 @@
# Issue #546 (https://github.com/rojo-rbx/rojo/issues/546)
Regression from Rojo 6.2.0 to Rojo 7.0.0. Meta files named as init.meta.json should apply after init.client.lua and other init files.

View File

@@ -0,0 +1,6 @@
{
"name": "issue_546",
"tree": {
"$path": "hello"
}
}

View File

@@ -0,0 +1 @@
print("Hello, world!")

View File

@@ -0,0 +1,5 @@
{
"properties": {
"Disabled": true
}
}

View File

@@ -291,7 +291,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
} }
}; };
let patch_set = compute_patch_set(snapshot.as_ref(), &tree, id); let patch_set = compute_patch_set(snapshot, &tree, id);
apply_patch_set(tree, patch_set) apply_patch_set(tree, patch_set)
} }
Ok(None) => { Ok(None) => {
@@ -334,7 +334,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
} }
}; };
let patch_set = compute_patch_set(snapshot.as_ref(), &tree, id); let patch_set = compute_patch_set(snapshot, &tree, id);
apply_patch_set(tree, patch_set) apply_patch_set(tree, patch_set)
} }
}; };

View File

@@ -1,12 +1,13 @@
use std::{ use std::{
io::{BufWriter, Write}, io::{BufWriter, Write},
mem::forget,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use anyhow::Context; use anyhow::Context;
use clap::Parser;
use fs_err::File; use fs_err::File;
use memofs::Vfs; use memofs::Vfs;
use structopt::StructOpt;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use crate::serve_session::ServeSession; use crate::serve_session::ServeSession;
@@ -17,20 +18,20 @@ const UNKNOWN_OUTPUT_KIND_ERR: &str = "Could not detect what kind of file to bui
Expected output file to end in .rbxl, .rbxlx, .rbxm, or .rbxmx."; Expected output file to end in .rbxl, .rbxlx, .rbxm, or .rbxmx.";
/// Generates a model or place file from the Rojo project. /// Generates a model or place file from the Rojo project.
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub struct BuildCommand { pub struct BuildCommand {
/// Path to the project to serve. Defaults to the current directory. /// Path to the project to serve. Defaults to the current directory.
#[structopt(default_value = "")] #[clap(default_value = "")]
pub project: PathBuf, pub project: PathBuf,
/// Where to output the result. /// Where to output the result.
/// ///
/// Should end in .rbxm, .rbxl, .rbxmx, or .rbxlx. /// Should end in .rbxm, .rbxl, .rbxmx, or .rbxlx.
#[structopt(long, short)] #[clap(long, short)]
pub output: PathBuf, pub output: PathBuf,
/// Whether to automatically rebuild when any input files change. /// Whether to automatically rebuild when any input files change.
#[structopt(long)] #[clap(long)]
pub watch: bool, pub watch: bool,
} }
@@ -61,6 +62,10 @@ impl BuildCommand {
} }
} }
// Avoid dropping ServeSession: it's potentially VERY expensive to drop
// and we're about to exit anyways.
forget(session);
Ok(()) Ok(())
} }
} }
@@ -97,6 +102,7 @@ fn xml_encode_config() -> rbx_xml::EncodeOptions {
rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown) rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown)
} }
#[profiling::function]
fn write_model( fn write_model(
session: &ServeSession, session: &ServeSession,
output: &Path, output: &Path,

View File

@@ -1,7 +1,7 @@
use structopt::StructOpt; use clap::Parser;
/// Open Rojo's documentation in your browser. /// Open Rojo's documentation in your browser.
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub struct DocCommand {} pub struct DocCommand {}
impl DocCommand { impl DocCommand {

View File

@@ -1,15 +1,15 @@
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::Context; use anyhow::Context;
use structopt::StructOpt; use clap::Parser;
use crate::project::Project; use crate::project::Project;
/// Reformat a Rojo project using the standard JSON formatting rules. /// Reformat a Rojo project using the standard JSON formatting rules.
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub struct FmtProjectCommand { pub struct FmtProjectCommand {
/// Path to the project to format. Defaults to the current directory. /// Path to the project to format. Defaults to the current directory.
#[structopt(default_value = "")] #[clap(default_value = "")]
pub project: PathBuf, pub project: PathBuf,
} }

View File

@@ -4,9 +4,9 @@ use std::process::{Command, Stdio};
use std::str::FromStr; use std::str::FromStr;
use anyhow::{bail, format_err}; use anyhow::{bail, format_err};
use clap::Parser;
use fs_err as fs; use fs_err as fs;
use fs_err::OpenOptions; use fs_err::OpenOptions;
use structopt::StructOpt;
use super::resolve_path; use super::resolve_path;
@@ -22,14 +22,14 @@ static PLACE_README: &str = include_str!("../../assets/default-place-project/REA
static PLACE_GIT_IGNORE: &str = include_str!("../../assets/default-place-project/gitignore.txt"); static PLACE_GIT_IGNORE: &str = include_str!("../../assets/default-place-project/gitignore.txt");
/// Initializes a new Rojo project. /// Initializes a new Rojo project.
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub struct InitCommand { pub struct InitCommand {
/// Path to the place to create the project. Defaults to the current directory. /// Path to the place to create the project. Defaults to the current directory.
#[structopt(default_value = "")] #[clap(default_value = "")]
pub path: PathBuf, pub path: PathBuf,
/// The kind of project to create, 'place' or 'model'. Defaults to place. /// The kind of project to create, 'place' or 'model'. Defaults to place.
#[structopt(long, default_value = "place")] #[clap(long, default_value = "place")]
pub kind: InitKind, pub kind: InitKind,
} }

View File

@@ -1,4 +1,4 @@
//! Defines Rojo's CLI through structopt types. //! Defines Rojo's CLI through clap types.
mod build; mod build;
mod doc; mod doc;
@@ -7,11 +7,12 @@ mod init;
mod plugin; mod plugin;
mod serve; mod serve;
mod sourcemap; mod sourcemap;
mod unpack;
mod upload; mod upload;
use std::{borrow::Cow, env, path::Path, str::FromStr}; use std::{borrow::Cow, env, path::Path, str::FromStr};
use structopt::StructOpt; use clap::Parser;
use thiserror::Error; use thiserror::Error;
pub use self::build::BuildCommand; pub use self::build::BuildCommand;
@@ -21,17 +22,18 @@ pub use self::init::{InitCommand, InitKind};
pub use self::plugin::{PluginCommand, PluginSubcommand}; pub use self::plugin::{PluginCommand, PluginSubcommand};
pub use self::serve::ServeCommand; pub use self::serve::ServeCommand;
pub use self::sourcemap::SourcemapCommand; pub use self::sourcemap::SourcemapCommand;
pub use self::unpack::UnpackCommand;
pub use self::upload::UploadCommand; pub use self::upload::UploadCommand;
/// Command line options that Rojo accepts, defined using the structopt crate. /// Command line options that Rojo accepts, defined using the clap crate.
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
#[structopt(name = "Rojo", about, author)] #[clap(name = "Rojo", version, about, author)]
pub struct Options { pub struct Options {
#[structopt(flatten)] #[clap(flatten)]
pub global: GlobalOptions, pub global: GlobalOptions,
/// Subcommand to run in this invocation. /// Subcommand to run in this invocation.
#[structopt(subcommand)] #[clap(subcommand)]
pub subcommand: Subcommand, pub subcommand: Subcommand,
} }
@@ -46,18 +48,19 @@ impl Options {
Subcommand::FmtProject(subcommand) => subcommand.run(), Subcommand::FmtProject(subcommand) => subcommand.run(),
Subcommand::Doc(subcommand) => subcommand.run(), Subcommand::Doc(subcommand) => subcommand.run(),
Subcommand::Plugin(subcommand) => subcommand.run(), Subcommand::Plugin(subcommand) => subcommand.run(),
Subcommand::Unpack(subcommand) => subcommand.run(),
} }
} }
} }
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub struct GlobalOptions { pub struct GlobalOptions {
/// Sets verbosity level. Can be specified multiple times. /// Sets verbosity level. Can be specified multiple times.
#[structopt(long("verbose"), short, global(true), parse(from_occurrences))] #[clap(long("verbose"), short, global(true), parse(from_occurrences))]
pub verbosity: u8, pub verbosity: u8,
/// Set color behavior. Valid values are auto, always, and never. /// Set color behavior. Valid values are auto, always, and never.
#[structopt(long("color"), global(true), default_value("auto"))] #[clap(long("color"), global(true), default_value("auto"))]
pub color: ColorChoice, pub color: ColorChoice,
} }
@@ -109,7 +112,7 @@ pub struct ColorChoiceParseError {
attempted: String, attempted: String,
} }
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub enum Subcommand { pub enum Subcommand {
Init(InitCommand), Init(InitCommand),
Serve(ServeCommand), Serve(ServeCommand),
@@ -119,6 +122,7 @@ pub enum Subcommand {
FmtProject(FmtProjectCommand), FmtProject(FmtProjectCommand),
Doc(DocCommand), Doc(DocCommand),
Plugin(PluginCommand), Plugin(PluginCommand),
Unpack(UnpackCommand),
} }
pub(super) fn resolve_path(path: &Path) -> Cow<'_, Path> { pub(super) fn resolve_path(path: &Path) -> Cow<'_, Path> {

View File

@@ -3,9 +3,9 @@ use std::{
io::BufWriter, io::BufWriter,
}; };
use clap::Parser;
use memofs::{InMemoryFs, Vfs, VfsSnapshot}; use memofs::{InMemoryFs, Vfs, VfsSnapshot};
use roblox_install::RobloxStudio; use roblox_install::RobloxStudio;
use structopt::StructOpt;
use crate::serve_session::ServeSession; use crate::serve_session::ServeSession;
@@ -13,14 +13,14 @@ static PLUGIN_BINCODE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/plugin.
static PLUGIN_FILE_NAME: &str = "RojoManagedPlugin.rbxm"; static PLUGIN_FILE_NAME: &str = "RojoManagedPlugin.rbxm";
/// Install Rojo's plugin. /// Install Rojo's plugin.
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub struct PluginCommand { pub struct PluginCommand {
#[structopt(subcommand)] #[clap(subcommand)]
subcommand: PluginSubcommand, subcommand: PluginSubcommand,
} }
/// Manages Rojo's Roblox Studio plugin. /// Manages Rojo's Roblox Studio plugin.
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub enum PluginSubcommand { pub enum PluginSubcommand {
/// Install the plugin in Roblox Studio's plugins folder. If the plugin is /// Install the plugin in Roblox Studio's plugins folder. If the plugin is
/// already installed, installing it again will overwrite the current plugin /// already installed, installing it again will overwrite the current plugin

View File

@@ -5,8 +5,8 @@ use std::{
sync::Arc, sync::Arc,
}; };
use clap::Parser;
use memofs::Vfs; use memofs::Vfs;
use structopt::StructOpt;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use crate::{serve_session::ServeSession, web::LiveServer}; use crate::{serve_session::ServeSession, web::LiveServer};
@@ -17,19 +17,19 @@ const DEFAULT_BIND_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
const DEFAULT_PORT: u16 = 34872; const DEFAULT_PORT: u16 = 34872;
/// Expose a Rojo project to the Rojo Studio plugin. /// Expose a Rojo project to the Rojo Studio plugin.
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub struct ServeCommand { pub struct ServeCommand {
/// Path to the project to serve. Defaults to the current directory. /// Path to the project to serve. Defaults to the current directory.
#[structopt(default_value = "")] #[clap(default_value = "")]
pub project: PathBuf, pub project: PathBuf,
/// The IP address to listen on. Defaults to `127.0.0.1`. /// The IP address to listen on. Defaults to `127.0.0.1`.
#[structopt(long)] #[clap(long)]
pub address: Option<IpAddr>, pub address: Option<IpAddr>,
/// The port to listen on. Defaults to the project's preference, or `34872` if /// The port to listen on. Defaults to the project's preference, or `34872` if
/// it has none. /// it has none.
#[structopt(long)] #[clap(long)]
pub port: Option<u16>, pub port: Option<u16>,
} }

View File

@@ -3,11 +3,11 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use clap::Parser;
use fs_err::File; use fs_err::File;
use memofs::Vfs; use memofs::Vfs;
use rbx_dom_weak::types::Ref; use rbx_dom_weak::types::Ref;
use serde::Serialize; use serde::Serialize;
use structopt::StructOpt;
use crate::{ use crate::{
serve_session::ServeSession, serve_session::ServeSession,
@@ -33,22 +33,22 @@ struct SourcemapNode {
} }
/// Generates a sourcemap file from the Rojo project. /// Generates a sourcemap file from the Rojo project.
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub struct SourcemapCommand { pub struct SourcemapCommand {
/// Path to the project to use for the sourcemap. Defaults to the current /// Path to the project to use for the sourcemap. Defaults to the current
/// directory. /// directory.
#[structopt(default_value = "")] #[clap(default_value = "")]
pub project: PathBuf, pub project: PathBuf,
/// Where to output the sourcemap. Omit this to use stdout instead of /// Where to output the sourcemap. Omit this to use stdout instead of
/// writing to a file. /// writing to a file.
/// ///
/// Should end in .json. /// Should end in .json.
#[structopt(long, short)] #[clap(long, short)]
pub output: Option<PathBuf>, pub output: Option<PathBuf>,
/// If non-script files should be included or not. Defaults to false. /// If non-script files should be included or not. Defaults to false.
#[structopt(long)] #[clap(long)]
pub include_non_scripts: bool, pub include_non_scripts: bool,
} }
@@ -56,9 +56,6 @@ impl SourcemapCommand {
pub fn run(self) -> anyhow::Result<()> { pub fn run(self) -> anyhow::Result<()> {
let project_path = resolve_path(&self.project); let project_path = resolve_path(&self.project);
let mut project_dir = project_path.to_path_buf();
project_dir.pop();
log::trace!("Constructing in-memory filesystem"); log::trace!("Constructing in-memory filesystem");
let vfs = Vfs::new_default(); let vfs = Vfs::new_default();
@@ -71,7 +68,7 @@ impl SourcemapCommand {
filter_non_scripts filter_non_scripts
}; };
let root_node = recurse_create_node(&tree, tree.get_root_id(), &project_dir, filter); let root_node = recurse_create_node(&tree, tree.get_root_id(), session.root_dir(), filter);
if let Some(output_path) = self.output { if let Some(output_path) = self.output {
let mut file = BufWriter::new(File::create(&output_path)?); let mut file = BufWriter::new(File::create(&output_path)?);

65
src/cli/unpack.rs Normal file
View File

@@ -0,0 +1,65 @@
use std::{io::BufReader, path::PathBuf};
use anyhow::bail;
use clap::Parser;
use fs_err::File;
use rbx_dom_weak::{Instance, WeakDom};
use crate::{Project, ProjectNode};
use super::resolve_path;
/// Unpack a Roblox place file into an existing Rojo project.
#[derive(Debug, Parser)]
pub struct UnpackCommand {
/// Path to the project to unpack. Defaults to the current directory.
#[clap(long, default_value = "")]
pub project: PathBuf,
/// Path to the place to unpack from.
pub place: PathBuf,
}
impl UnpackCommand {
pub fn run(self) -> anyhow::Result<()> {
let project_path = resolve_path(&self.project);
let project = match Project::load_fuzzy(&project_path)? {
Some(project) => project,
None => bail!("No project file was found; rojo unpack requires a project file."),
};
let place_ext = self
.place
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.to_lowercase());
let file = BufReader::new(File::open(&self.place)?);
let dom = match place_ext.as_deref() {
Some("rbxl") => rbx_binary::from_reader(file)?,
Some("rbxlx") => rbx_xml::from_reader_default(file)?,
Some(_) | None => bail!("Place files must end in .rbxl or .rbxlx"),
};
let context = Context { project, dom };
context.unpack();
Ok(())
}
}
struct Context {
project: Project,
dom: WeakDom,
}
impl Context {
fn unpack(&self) {
self.unpack_node(&self.project.tree, self.dom.root());
}
fn unpack_node(&self, node: &ProjectNode, instance: &Instance) {
// TODO
}
}

View File

@@ -2,38 +2,38 @@ use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use anyhow::{bail, format_err, Context}; use anyhow::{bail, format_err, Context};
use clap::Parser;
use memofs::Vfs; use memofs::Vfs;
use reqwest::{ use reqwest::{
header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT}, header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT},
StatusCode, StatusCode,
}; };
use structopt::StructOpt;
use crate::{auth_cookie::get_auth_cookie, serve_session::ServeSession}; use crate::{auth_cookie::get_auth_cookie, serve_session::ServeSession};
use super::resolve_path; use super::resolve_path;
/// Builds the project and uploads it to Roblox. /// Builds the project and uploads it to Roblox.
#[derive(Debug, StructOpt)] #[derive(Debug, Parser)]
pub struct UploadCommand { pub struct UploadCommand {
/// Path to the project to upload. Defaults to the current directory. /// Path to the project to upload. Defaults to the current directory.
#[structopt(default_value = "")] #[clap(default_value = "")]
pub project: PathBuf, pub project: PathBuf,
/// Authenication cookie to use. If not specified, Rojo will attempt to find one from the system automatically. /// Authenication cookie to use. If not specified, Rojo will attempt to find one from the system automatically.
#[structopt(long)] #[clap(long)]
pub cookie: Option<String>, pub cookie: Option<String>,
/// API key obtained from create.roblox.com/credentials. Rojo will use the Open Cloud API when this is provided. Only supports uploading to a place. /// API key obtained from create.roblox.com/credentials. Rojo will use the Open Cloud API when this is provided. Only supports uploading to a place.
#[structopt(long = "api_key")] #[clap(long = "api_key")]
pub api_key: Option<String>, pub api_key: Option<String>,
/// The Universe ID of the given place. Required when using the Open Cloud API. /// The Universe ID of the given place. Required when using the Open Cloud API.
#[structopt(long = "universe_id")] #[clap(long = "universe_id")]
pub universe_id: Option<u64>, pub universe_id: Option<u64>,
/// Asset ID to upload to. /// Asset ID to upload to.
#[structopt(long = "asset_id")] #[clap(long = "asset_id")]
pub asset_id: u64, pub asset_id: u64,
} }
@@ -123,7 +123,7 @@ fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()>
asset_id asset_id
); );
let client = reqwest::Client::new(); let client = reqwest::blocking::Client::new();
let build_request = move || { let build_request = move || {
client client
@@ -172,10 +172,10 @@ fn do_upload_open_cloud(
universe_id, asset_id universe_id, asset_id
); );
let client = reqwest::Client::new(); let client = reqwest::blocking::Client::new();
log::debug!("Uploading to Roblox..."); log::debug!("Uploading to Roblox...");
let mut response = client let response = client
.post(&url) .post(&url)
.header("x-api-key", api_key) .header("x-api-key", api_key)
.header(CONTENT_TYPE, "application/xml") .header(CONTENT_TYPE, "application/xml")

View File

@@ -1,11 +1,14 @@
use std::{env, panic, process}; use std::{env, panic, process};
use backtrace::Backtrace; use backtrace::Backtrace;
use structopt::StructOpt; use clap::Parser;
use librojo::cli::Options; use librojo::cli::Options;
fn main() { fn main() {
#[cfg(feature = "profile-with-tracy")]
tracy_client::Client::start();
panic::set_hook(Box::new(|panic_info| { panic::set_hook(Box::new(|panic_info| {
// PanicInfo's payload is usually a &'static str or String. // PanicInfo's payload is usually a &'static str or String.
// See: https://doc.rust-lang.org/beta/std/panic/struct.PanicInfo.html#method.payload // See: https://doc.rust-lang.org/beta/std/panic/struct.PanicInfo.html#method.payload
@@ -49,7 +52,7 @@ fn main() {
process::exit(1); process::exit(1);
})); }));
let options = Options::from_args(); let options = Options::parse();
let log_filter = match options.global.verbosity { let log_filter = match options.global.verbosity {
0 => "info", 0 => "info",

View File

@@ -64,6 +64,7 @@ impl<T: Clone> MessageQueue<T> {
/// This method is only useful in tests. Non-test code should use subscribe /// This method is only useful in tests. Non-test code should use subscribe
/// instead. /// instead.
#[cfg(test)] #[cfg(test)]
#[allow(unused)]
pub fn subscribe_any(&self) -> oneshot::Receiver<(u32, Vec<T>)> { pub fn subscribe_any(&self) -> oneshot::Receiver<(u32, Vec<T>)> {
let cursor = { let cursor = {
let messages = self.messages.read().unwrap(); let messages = self.messages.read().unwrap();

View File

@@ -36,17 +36,3 @@ where
seq.end() seq.end()
} }
pub fn serialize_option_absolute<S, T>(
maybe_path: &Option<T>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<Path>,
{
match maybe_path {
Some(path) => serialize_absolute(path, serializer),
None => serializer.serialize_none(),
}
}

View File

@@ -130,7 +130,7 @@ impl ServeSession {
let snapshot = snapshot_from_vfs(&instance_context, &vfs, &start_path)?; let snapshot = snapshot_from_vfs(&instance_context, &vfs, &start_path)?;
log::trace!("Computing initial patch set"); log::trace!("Computing initial patch set");
let patch_set = compute_patch_set(snapshot.as_ref(), &tree, root_id); let patch_set = compute_patch_set(snapshot, &tree, root_id);
log::trace!("Applying initial patch set"); log::trace!("Applying initial patch set");
apply_patch_set(&mut tree, patch_set); apply_patch_set(&mut tree, patch_set);
@@ -216,6 +216,10 @@ impl ServeSession {
pub fn serve_address(&self) -> Option<IpAddr> { pub fn serve_address(&self) -> Option<IpAddr> {
self.root_project.serve_address self.root_project.serve_address
} }
pub fn root_dir(&self) -> &Path {
self.root_project.folder_location()
}
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]

View File

@@ -4,7 +4,7 @@ use std::{borrow::Cow, collections::HashMap};
use rbx_dom_weak::{ use rbx_dom_weak::{
types::{Ref, Variant}, types::{Ref, Variant},
WeakDom, Instance, WeakDom,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -102,22 +102,29 @@ impl InstanceSnapshot {
} }
} }
pub fn from_tree(tree: &WeakDom, id: Ref) -> Self { #[profiling::function]
let instance = tree.get_by_ref(id).expect("instance did not exist in tree"); pub fn from_tree(tree: WeakDom, id: Ref) -> Self {
let (_, mut raw_tree) = tree.into_raw();
Self::from_raw_tree(&mut raw_tree, id)
}
fn from_raw_tree(raw_tree: &mut HashMap<Ref, Instance>, id: Ref) -> Self {
let instance = raw_tree
.remove(&id)
.expect("instance did not exist in tree");
let children = instance let children = instance
.children() .children()
.iter() .iter()
.copied() .map(|&id| Self::from_raw_tree(raw_tree, id))
.map(|id| Self::from_tree(tree, id))
.collect(); .collect();
Self { Self {
snapshot_id: Some(id), snapshot_id: Some(id),
metadata: InstanceMetadata::default(), metadata: InstanceMetadata::default(),
name: Cow::Owned(instance.name.clone()), name: Cow::Owned(instance.name),
class_name: Cow::Owned(instance.class.clone()), class_name: Cow::Owned(instance.class),
properties: instance.properties.clone(), properties: instance.properties,
children, children,
} }
} }

View File

@@ -1,6 +1,9 @@
//! Defines the algorithm for applying generated patches. //! Defines the algorithm for applying generated patches.
use std::collections::HashMap; use std::{
collections::{HashMap, HashSet},
mem::take,
};
use rbx_dom_weak::types::{Ref, Variant}; use rbx_dom_weak::types::{Ref, Variant};
@@ -12,21 +15,31 @@ use super::{
/// Consumes the input `PatchSet`, applying all of its prescribed changes to the /// Consumes the input `PatchSet`, applying all of its prescribed changes to the
/// tree and returns an `AppliedPatchSet`, which can be used to keep another /// tree and returns an `AppliedPatchSet`, which can be used to keep another
/// tree in sync with Rojo's. /// tree in sync with Rojo's.
#[profiling::function]
pub fn apply_patch_set(tree: &mut RojoTree, patch_set: PatchSet) -> AppliedPatchSet { pub fn apply_patch_set(tree: &mut RojoTree, patch_set: PatchSet) -> AppliedPatchSet {
let mut context = PatchApplyContext::default(); let mut context = PatchApplyContext::default();
for removed_id in patch_set.removed_instances { {
apply_remove_instance(&mut context, tree, removed_id); profiling::scope!("removals");
for removed_id in patch_set.removed_instances {
apply_remove_instance(&mut context, tree, removed_id);
}
} }
for add_patch in patch_set.added_instances { {
apply_add_child(&mut context, tree, add_patch.parent_id, add_patch.instance); profiling::scope!("additions");
for add_patch in patch_set.added_instances {
apply_add_child(&mut context, tree, add_patch.parent_id, add_patch.instance);
}
} }
// Updates need to be applied after additions, which reduces the complexity {
// of updates significantly. profiling::scope!("updates");
for update_patch in patch_set.updated_instances { // Updates need to be applied after additions, which reduces the complexity
apply_update_child(&mut context, tree, update_patch); // of updates significantly.
for update_patch in patch_set.updated_instances {
apply_update_child(&mut context, tree, update_patch);
}
} }
finalize_patch_application(context, tree) finalize_patch_application(context, tree)
@@ -55,20 +68,9 @@ struct PatchApplyContext {
/// eachother. /// eachother.
snapshot_id_to_instance_id: HashMap<Ref, Ref>, snapshot_id_to_instance_id: HashMap<Ref, Ref>,
/// The properties of instances added by the current `PatchSet`. /// Tracks all of the instances added by this patch that have refs that need
/// /// to be rewritten.
/// Instances added to the tree can refer to eachother via Ref properties, has_refs_to_rewrite: HashSet<Ref>,
/// but we need to make sure they're correctly transformed from snapshot
/// space into tree space (via `snapshot_id_to_instance_id`).
///
/// It's not possible to do that transformation for refs that refer to added
/// instances until all the instances have actually been inserted into the
/// tree. For simplicity, we defer application of _all_ properties on added
/// instances instead of just Refs.
///
/// This doesn't affect updated instances, since they're always applied
/// after we've added all the instances from the patch.
added_instance_properties: HashMap<Ref, HashMap<String, Variant>>,
/// The current applied patch result, describing changes made to the tree. /// The current applied patch result, describing changes made to the tree.
applied_patch_set: AppliedPatchSet, applied_patch_set: AppliedPatchSet,
@@ -84,23 +86,22 @@ struct PatchApplyContext {
/// The remaining Ref properties need to be handled during patch application, /// The remaining Ref properties need to be handled during patch application,
/// where we build up a map of snapshot IDs to instance IDs as they're created, /// where we build up a map of snapshot IDs to instance IDs as they're created,
/// then apply properties all at once at the end. /// then apply properties all at once at the end.
#[profiling::function]
fn finalize_patch_application(context: PatchApplyContext, tree: &mut RojoTree) -> AppliedPatchSet { fn finalize_patch_application(context: PatchApplyContext, tree: &mut RojoTree) -> AppliedPatchSet {
for (id, properties) in context.added_instance_properties { for id in context.has_refs_to_rewrite {
// This should always succeed since instances marked as added in our // This should always succeed since instances marked as added in our
// patch should be added without fail. // patch should be added without fail.
let mut instance = tree let mut instance = tree
.get_instance_mut(id) .get_instance_mut(id)
.expect("Invalid instance ID in deferred property map"); .expect("Invalid instance ID in deferred property map");
for (key, mut property_value) in properties { for value in instance.properties_mut().values_mut() {
if let Variant::Ref(referent) = property_value { if let Variant::Ref(referent) = value {
if let Some(&instance_referent) = context.snapshot_id_to_instance_id.get(&referent) if let Some(&instance_referent) = context.snapshot_id_to_instance_id.get(&referent)
{ {
property_value = Variant::Ref(instance_referent); *value = Variant::Ref(instance_referent);
} }
} }
instance.properties_mut().insert(key, property_value);
} }
} }
@@ -116,24 +117,24 @@ fn apply_add_child(
context: &mut PatchApplyContext, context: &mut PatchApplyContext,
tree: &mut RojoTree, tree: &mut RojoTree,
parent_id: Ref, parent_id: Ref,
snapshot: InstanceSnapshot, mut snapshot: InstanceSnapshot,
) { ) {
let snapshot_id = snapshot.snapshot_id; let snapshot_id = snapshot.snapshot_id;
let properties = snapshot.properties; let children = take(&mut snapshot.children);
let children = snapshot.children;
// Property application is deferred until after all children // If an object we're adding has a non-null referent, we'll note this
// are constructed. This helps apply referents correctly. // instance down as needing to be revisited later.
let remaining_snapshot = InstanceSnapshot::new() let has_refs = snapshot.properties.values().any(|value| match value {
.name(snapshot.name) Variant::Ref(value) => value.is_some(),
.class_name(snapshot.class_name) _ => false,
.metadata(snapshot.metadata) });
.snapshot_id(snapshot.snapshot_id);
let id = tree.insert_instance(parent_id, remaining_snapshot); let id = tree.insert_instance(parent_id, snapshot);
context.applied_patch_set.added.push(id); context.applied_patch_set.added.push(id);
context.added_instance_properties.insert(id, properties); if has_refs {
context.has_refs_to_rewrite.insert(id);
}
if let Some(snapshot_id) = snapshot_id { if let Some(snapshot_id) = snapshot_id {
context.snapshot_id_to_instance_id.insert(snapshot_id, id); context.snapshot_id_to_instance_id.insert(snapshot_id, id);

View File

@@ -1,7 +1,10 @@
//! Defines the algorithm for computing a roughly-minimal patch set given an //! Defines the algorithm for computing a roughly-minimal patch set given an
//! existing instance tree and an instance snapshot. //! existing instance tree and an instance snapshot.
use std::collections::{HashMap, HashSet}; use std::{
collections::{HashMap, HashSet},
mem::take,
};
use rbx_dom_weak::types::{Ref, Variant}; use rbx_dom_weak::types::{Ref, Variant};
@@ -10,11 +13,8 @@ use super::{
InstanceSnapshot, InstanceWithMeta, RojoTree, InstanceSnapshot, InstanceWithMeta, RojoTree,
}; };
pub fn compute_patch_set( #[profiling::function]
snapshot: Option<&InstanceSnapshot>, pub fn compute_patch_set(snapshot: Option<InstanceSnapshot>, tree: &RojoTree, id: Ref) -> PatchSet {
tree: &RojoTree,
id: Ref,
) -> PatchSet {
let mut patch_set = PatchSet::new(); let mut patch_set = PatchSet::new();
if let Some(snapshot) = snapshot { if let Some(snapshot) = snapshot {
@@ -74,7 +74,7 @@ fn rewrite_refs_in_snapshot(context: &ComputePatchContext, snapshot: &mut Instan
fn compute_patch_set_internal( fn compute_patch_set_internal(
context: &mut ComputePatchContext, context: &mut ComputePatchContext,
snapshot: &InstanceSnapshot, mut snapshot: InstanceSnapshot,
tree: &RojoTree, tree: &RojoTree,
id: Ref, id: Ref,
patch_set: &mut PatchSet, patch_set: &mut PatchSet,
@@ -87,12 +87,12 @@ fn compute_patch_set_internal(
.get_instance(id) .get_instance(id)
.expect("Instance did not exist in tree"); .expect("Instance did not exist in tree");
compute_property_patches(snapshot, &instance, patch_set); compute_property_patches(&mut snapshot, &instance, patch_set);
compute_children_patches(context, snapshot, tree, id, patch_set); compute_children_patches(context, &mut snapshot, tree, id, patch_set);
} }
fn compute_property_patches( fn compute_property_patches(
snapshot: &InstanceSnapshot, snapshot: &mut InstanceSnapshot,
instance: &InstanceWithMeta, instance: &InstanceWithMeta,
patch_set: &mut PatchSet, patch_set: &mut PatchSet,
) { ) {
@@ -102,32 +102,32 @@ fn compute_property_patches(
let changed_name = if snapshot.name == instance.name() { let changed_name = if snapshot.name == instance.name() {
None None
} else { } else {
Some(snapshot.name.clone().into_owned()) Some(take(&mut snapshot.name).into_owned())
}; };
let changed_class_name = if snapshot.class_name == instance.class_name() { let changed_class_name = if snapshot.class_name == instance.class_name() {
None None
} else { } else {
Some(snapshot.class_name.clone().into_owned()) Some(take(&mut snapshot.class_name).into_owned())
}; };
let changed_metadata = if &snapshot.metadata == instance.metadata() { let changed_metadata = if &snapshot.metadata == instance.metadata() {
None None
} else { } else {
Some(snapshot.metadata.clone()) Some(take(&mut snapshot.metadata))
}; };
for (name, snapshot_value) in &snapshot.properties { for (name, snapshot_value) in take(&mut snapshot.properties) {
visited_properties.insert(name.as_str()); visited_properties.insert(name.clone());
match instance.properties().get(name) { match instance.properties().get(&name) {
Some(instance_value) => { Some(instance_value) => {
if snapshot_value != instance_value { if &snapshot_value != instance_value {
changed_properties.insert(name.clone(), Some(snapshot_value.clone())); changed_properties.insert(name, Some(snapshot_value));
} }
} }
None => { None => {
changed_properties.insert(name.clone(), Some(snapshot_value.clone())); changed_properties.insert(name, Some(snapshot_value));
} }
} }
} }
@@ -159,7 +159,7 @@ fn compute_property_patches(
fn compute_children_patches( fn compute_children_patches(
context: &mut ComputePatchContext, context: &mut ComputePatchContext,
snapshot: &InstanceSnapshot, snapshot: &mut InstanceSnapshot,
tree: &RojoTree, tree: &RojoTree,
id: Ref, id: Ref,
patch_set: &mut PatchSet, patch_set: &mut PatchSet,
@@ -172,7 +172,7 @@ fn compute_children_patches(
let mut paired_instances = vec![false; instance_children.len()]; let mut paired_instances = vec![false; instance_children.len()];
for snapshot_child in snapshot.children.iter() { for snapshot_child in take(&mut snapshot.children) {
let matching_instance = let matching_instance =
instance_children instance_children
.iter() .iter()
@@ -209,7 +209,7 @@ fn compute_children_patches(
None => { None => {
patch_set.added_instances.push(PatchAdd { patch_set.added_instances.push(PatchAdd {
parent_id: id, parent_id: id,
instance: snapshot_child.clone(), instance: snapshot_child,
}); });
} }
} }
@@ -257,7 +257,7 @@ mod test {
children: Vec::new(), children: Vec::new(),
}; };
let patch_set = compute_patch_set(Some(&snapshot), &tree, root_id); let patch_set = compute_patch_set(Some(snapshot), &tree, root_id);
let expected_patch_set = PatchSet { let expected_patch_set = PatchSet {
updated_instances: vec![PatchUpdate { updated_instances: vec![PatchUpdate {
@@ -307,7 +307,7 @@ mod test {
class_name: Cow::Borrowed("foo"), class_name: Cow::Borrowed("foo"),
}; };
let patch_set = compute_patch_set(Some(&snapshot), &tree, root_id); let patch_set = compute_patch_set(Some(snapshot), &tree, root_id);
let expected_patch_set = PatchSet { let expected_patch_set = PatchSet {
added_instances: vec![PatchAdd { added_instances: vec![PatchAdd {

View File

@@ -23,7 +23,7 @@ fn set_name_and_class_name() {
children: Vec::new(), children: Vec::new(),
}; };
let patch_set = compute_patch_set(Some(&snapshot), &tree, tree.get_root_id()); let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
let patch_value = redactions.redacted_yaml(patch_set); let patch_value = redactions.redacted_yaml(patch_set);
assert_yaml_snapshot!(patch_value); assert_yaml_snapshot!(patch_value);
@@ -47,7 +47,7 @@ fn set_property() {
children: Vec::new(), children: Vec::new(),
}; };
let patch_set = compute_patch_set(Some(&snapshot), &tree, tree.get_root_id()); let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
let patch_value = redactions.redacted_yaml(patch_set); let patch_value = redactions.redacted_yaml(patch_set);
assert_yaml_snapshot!(patch_value); assert_yaml_snapshot!(patch_value);
@@ -78,7 +78,7 @@ fn remove_property() {
children: Vec::new(), children: Vec::new(),
}; };
let patch_set = compute_patch_set(Some(&snapshot), &tree, tree.get_root_id()); let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
let patch_value = redactions.redacted_yaml(patch_set); let patch_value = redactions.redacted_yaml(patch_set);
assert_yaml_snapshot!(patch_value); assert_yaml_snapshot!(patch_value);
@@ -107,7 +107,7 @@ fn add_child() {
}], }],
}; };
let patch_set = compute_patch_set(Some(&snapshot), &tree, tree.get_root_id()); let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
let patch_value = redactions.redacted_yaml(patch_set); let patch_value = redactions.redacted_yaml(patch_set);
assert_yaml_snapshot!(patch_value); assert_yaml_snapshot!(patch_value);
@@ -139,7 +139,7 @@ fn remove_child() {
children: Vec::new(), children: Vec::new(),
}; };
let patch_set = compute_patch_set(Some(&snapshot), &tree, tree.get_root_id()); let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
let patch_value = redactions.redacted_yaml(patch_set); let patch_value = redactions.redacted_yaml(patch_set);
assert_yaml_snapshot!(patch_value); assert_yaml_snapshot!(patch_value);

View File

@@ -87,8 +87,9 @@ impl RojoTree {
} }
pub fn insert_instance(&mut self, parent_ref: Ref, snapshot: InstanceSnapshot) -> Ref { pub fn insert_instance(&mut self, parent_ref: Ref, snapshot: InstanceSnapshot) -> Ref {
let builder = InstanceBuilder::new(snapshot.class_name.to_owned()) let builder = InstanceBuilder::empty()
.with_name(snapshot.name.to_owned()) .with_class(snapshot.class_name.into_owned())
.with_name(snapshot.name.into_owned())
.with_properties(snapshot.properties); .with_properties(snapshot.properties);
let referent = self.inner.insert(parent_ref, builder); let referent = self.inner.insert(parent_ref, builder);

View File

@@ -10,6 +10,40 @@ pub fn snapshot_dir(
context: &InstanceContext, context: &InstanceContext,
vfs: &Vfs, vfs: &Vfs,
path: &Path, path: &Path,
) -> anyhow::Result<Option<InstanceSnapshot>> {
let mut snapshot = match snapshot_dir_no_meta(context, vfs, path)? {
Some(snapshot) => snapshot,
None => return Ok(None),
};
if let Some(mut meta) = dir_meta(vfs, path)? {
meta.apply_all(&mut snapshot)?;
}
Ok(Some(snapshot))
}
/// Retrieves the meta file that should be applied for this directory, if it
/// exists.
pub fn dir_meta(vfs: &Vfs, path: &Path) -> anyhow::Result<Option<DirectoryMetadata>> {
let meta_path = path.join("init.meta.json");
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
let metadata = DirectoryMetadata::from_slice(&meta_contents, meta_path)?;
Ok(Some(metadata))
} else {
Ok(None)
}
}
/// Snapshot a directory without applying meta files; useful for if the
/// directory's ClassName will change before metadata should be applied. For
/// example, this can happen if the directory contains an `init.client.lua`
/// file.
pub fn snapshot_dir_no_meta(
context: &InstanceContext,
vfs: &Vfs,
path: &Path,
) -> anyhow::Result<Option<InstanceSnapshot>> { ) -> anyhow::Result<Option<InstanceSnapshot>> {
let passes_filter_rules = |child: &DirEntry| { let passes_filter_rules = |child: &DirEntry| {
context context
@@ -52,7 +86,7 @@ pub fn snapshot_dir(
path.join("init.client.lua"), path.join("init.client.lua"),
]; ];
let mut snapshot = InstanceSnapshot::new() let snapshot = InstanceSnapshot::new()
.name(instance_name) .name(instance_name)
.class_name("Folder") .class_name("Folder")
.children(snapshot_children) .children(snapshot_children)
@@ -63,11 +97,6 @@ pub fn snapshot_dir(
.context(context), .context(context),
); );
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
let mut metadata = DirectoryMetadata::from_slice(&meta_contents, meta_path)?;
metadata.apply_all(&mut snapshot)?;
}
Ok(Some(snapshot)) Ok(Some(snapshot))
} }

View File

@@ -6,7 +6,11 @@ use memofs::{IoResultExt, Vfs};
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot}; use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
use super::{dir::snapshot_dir, meta_file::AdjacentMetadata, util::match_trailing}; use super::{
dir::{dir_meta, snapshot_dir_no_meta},
meta_file::AdjacentMetadata,
util::match_trailing,
};
/// Core routine for turning Lua files into snapshots. /// Core routine for turning Lua files into snapshots.
pub fn snapshot_lua( pub fn snapshot_lua(
@@ -66,7 +70,7 @@ pub fn snapshot_lua_init(
init_path: &Path, init_path: &Path,
) -> anyhow::Result<Option<InstanceSnapshot>> { ) -> anyhow::Result<Option<InstanceSnapshot>> {
let folder_path = init_path.parent().unwrap(); let folder_path = init_path.parent().unwrap();
let dir_snapshot = snapshot_dir(context, vfs, folder_path)?.unwrap(); let dir_snapshot = snapshot_dir_no_meta(context, vfs, folder_path)?.unwrap();
if dir_snapshot.class_name != "Folder" { if dir_snapshot.class_name != "Folder" {
anyhow::bail!( anyhow::bail!(
@@ -86,6 +90,10 @@ pub fn snapshot_lua_init(
init_snapshot.children = dir_snapshot.children; init_snapshot.children = dir_snapshot.children;
init_snapshot.metadata = dir_snapshot.metadata; init_snapshot.metadata = dir_snapshot.metadata;
if let Some(mut meta) = dir_meta(vfs, folder_path)? {
meta.apply_all(&mut init_snapshot)?;
}
Ok(Some(init_snapshot)) Ok(Some(init_snapshot))
} }

View File

@@ -40,6 +40,7 @@ pub use self::project::snapshot_project_node;
/// The main entrypoint to the snapshot function. This function can be pointed /// The main entrypoint to the snapshot function. This function can be pointed
/// at any path and will return something if Rojo knows how to deal with it. /// at any path and will return something if Rojo knows how to deal with it.
#[profiling::function]
pub fn snapshot_from_vfs( pub fn snapshot_from_vfs(
context: &InstanceContext, context: &InstanceContext,
vfs: &Vfs, vfs: &Vfs,

View File

@@ -7,6 +7,7 @@ use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
use super::util::PathExt; use super::util::PathExt;
#[profiling::function]
pub fn snapshot_rbxm( pub fn snapshot_rbxm(
context: &InstanceContext, context: &InstanceContext,
vfs: &Vfs, vfs: &Vfs,
@@ -21,7 +22,8 @@ pub fn snapshot_rbxm(
let children = root_instance.children(); let children = root_instance.children();
if children.len() == 1 { if children.len() == 1 {
let snapshot = InstanceSnapshot::from_tree(&temp_tree, children[0]) let child = children[0];
let snapshot = InstanceSnapshot::from_tree(temp_tree, child)
.name(name) .name(name)
.metadata( .metadata(
InstanceMetadata::new() InstanceMetadata::new()

View File

@@ -24,7 +24,8 @@ pub fn snapshot_rbxmx(
let children = root_instance.children(); let children = root_instance.children();
if children.len() == 1 { if children.len() == 1 {
let snapshot = InstanceSnapshot::from_tree(&temp_tree, children[0]) let child = children[0];
let snapshot = InstanceSnapshot::from_tree(temp_tree, child)
.name(name) .name(name)
.metadata( .metadata(
InstanceMetadata::new() InstanceMetadata::new()

View File

@@ -141,14 +141,14 @@ impl TestServeSession {
pub fn get_api_rojo(&self) -> Result<ServerInfoResponse, reqwest::Error> { pub fn get_api_rojo(&self) -> Result<ServerInfoResponse, reqwest::Error> {
let url = format!("http://localhost:{}/api/rojo", self.port); let url = format!("http://localhost:{}/api/rojo", self.port);
let body = reqwest::get(&url)?.text()?; let body = reqwest::blocking::get(&url)?.text()?;
Ok(serde_json::from_str(&body).expect("Server returned malformed response")) Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
} }
pub fn get_api_read(&self, id: Ref) -> Result<ReadResponse, reqwest::Error> { pub fn get_api_read(&self, id: Ref) -> Result<ReadResponse, reqwest::Error> {
let url = format!("http://localhost:{}/api/read/{}", self.port, id); let url = format!("http://localhost:{}/api/read/{}", self.port, id);
let body = reqwest::get(&url)?.text()?; let body = reqwest::blocking::get(&url)?.text()?;
Ok(serde_json::from_str(&body).expect("Server returned malformed response")) Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
} }
@@ -159,7 +159,7 @@ impl TestServeSession {
) -> Result<SubscribeResponse<'static>, reqwest::Error> { ) -> Result<SubscribeResponse<'static>, reqwest::Error> {
let url = format!("http://localhost:{}/api/subscribe/{}", self.port, cursor); let url = format!("http://localhost:{}/api/subscribe/{}", self.port, cursor);
reqwest::get(&url)?.json() reqwest::blocking::get(&url)?.json()
} }
} }

View File

@@ -36,11 +36,13 @@ gen_build_tests! {
init_meta_class_name, init_meta_class_name,
init_meta_properties, init_meta_properties,
init_with_children, init_with_children,
issue_546,
json_as_lua, json_as_lua,
json_model_in_folder, json_model_in_folder,
json_model_legacy_name, json_model_legacy_name,
module_in_folder, module_in_folder,
module_init, module_init,
optional,
project_composed_default, project_composed_default,
project_composed_file, project_composed_file,
project_root_name, project_root_name,
@@ -53,7 +55,6 @@ gen_build_tests! {
txt, txt,
txt_in_folder, txt_in_folder,
unresolved_values, unresolved_values,
optional,
weldconstraint, weldconstraint,
} }