mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 20:55:50 +00:00
Compare commits
8 Commits
aarch-wind
...
v7.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af9629c53f | ||
|
|
9509909f46 | ||
|
|
88efbd433f | ||
|
|
f716928683 | ||
|
|
e23d024ba3 | ||
|
|
591419611e | ||
|
|
f68beab1df | ||
|
|
2798610afd |
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
||||
run: cargo build --locked --verbose
|
||||
|
||||
lint:
|
||||
name: Rustfmt, Clippy, & Stylua
|
||||
name: Rustfmt, Clippy, Stylua, & Selene
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -98,6 +98,9 @@ jobs:
|
||||
- name: Stylua
|
||||
run: stylua --check plugin/src
|
||||
|
||||
- name: Selene
|
||||
run: selene plugin/src
|
||||
|
||||
- name: Rustfmt
|
||||
run: cargo fmt -- --check
|
||||
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -2,6 +2,28 @@
|
||||
|
||||
## Unreleased Changes
|
||||
|
||||
## [7.4.1] - February 20, 2024
|
||||
* Made the `name` field optional on project files ([#870])
|
||||
Files named `default.project.json` inherit the name of the folder they're in and all other projects
|
||||
are named as expect (e.g. `foo.project.json` becomes an Instance named `foo`)
|
||||
|
||||
There is no change in behavior if `name` is set.
|
||||
* Fixed incorrect results when building model pivots ([#865])
|
||||
* Fixed incorrect results when serving model pivots ([#868])
|
||||
* Rojo now converts any line endings to LF, preventing spurious diffs when syncing Lua files on Windows ([#854])
|
||||
* Fixed Rojo plugin failing to connect when project contains certain unreadable properties ([#848])
|
||||
* Fixed various cases where patch visualizer would not display sync failures ([#845], [#844])
|
||||
* Fixed http error handling so Rojo can be used in Github Codespaces ([#847])
|
||||
|
||||
[#848]: https://github.com/rojo-rbx/rojo/pull/848
|
||||
[#845]: https://github.com/rojo-rbx/rojo/pull/845
|
||||
[#844]: https://github.com/rojo-rbx/rojo/pull/844
|
||||
[#847]: https://github.com/rojo-rbx/rojo/pull/847
|
||||
[#854]: https://github.com/rojo-rbx/rojo/pull/854
|
||||
[#865]: https://github.com/rojo-rbx/rojo/pull/865
|
||||
[#868]: https://github.com/rojo-rbx/rojo/pull/868
|
||||
[#870]: https://github.com/rojo-rbx/rojo/pull/870
|
||||
|
||||
## [7.4.0] - January 16, 2024
|
||||
* Improved the visualization for array properties like Tags ([#829])
|
||||
* Significantly improved performance of `rojo serve`, `rojo build --watch`, and `rojo sourcemap --watch` on macOS. ([#830])
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1831,7 +1831,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rojo"
|
||||
version = "7.4.0"
|
||||
version = "7.4.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"backtrace",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rojo"
|
||||
version = "7.4.0"
|
||||
version = "7.4.1"
|
||||
rust-version = "1.70.0"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
description = "Enables professional-grade development tools for Roblox developers"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[tools]
|
||||
rojo = "rojo-rbx/rojo@7.3.0"
|
||||
selene = "Kampfkarren/selene@0.25.0"
|
||||
selene = "Kampfkarren/selene@0.26.1"
|
||||
stylua = "JohnnyMorganz/stylua@0.18.2"
|
||||
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
## Unreleased Changes
|
||||
* Changed `StdBackend` file watching component to use minimal recursive watches. [#830]
|
||||
* Added `Vfs::read_to_string` and `Vfs::read_to_string_lf_normalized` [#854]
|
||||
|
||||
[#830]: https://github.com/rojo-rbx/rojo/pull/830
|
||||
[#854]: https://github.com/rojo-rbx/rojo/pull/854
|
||||
|
||||
## 0.2.0 (2021-08-23)
|
||||
* Updated to `crossbeam-channel` 0.5.1.
|
||||
|
||||
@@ -22,9 +22,9 @@ mod noop_backend;
|
||||
mod snapshot;
|
||||
mod std_backend;
|
||||
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::{io, str};
|
||||
|
||||
pub use in_memory_fs::InMemoryFs;
|
||||
pub use noop_backend::NoopBackend;
|
||||
@@ -155,6 +155,24 @@ impl VfsInner {
|
||||
Ok(Arc::new(contents))
|
||||
}
|
||||
|
||||
fn read_to_string<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Arc<String>> {
|
||||
let path = path.as_ref();
|
||||
let contents = self.backend.read(path)?;
|
||||
|
||||
if self.watch_enabled {
|
||||
self.backend.watch(path)?;
|
||||
}
|
||||
|
||||
let contents_str = str::from_utf8(&contents).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("File was not valid UTF-8: {}", path.display()),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Arc::new(contents_str.into()))
|
||||
}
|
||||
|
||||
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&mut self, path: P, contents: C) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let contents = contents.as_ref();
|
||||
@@ -258,6 +276,33 @@ impl Vfs {
|
||||
self.inner.lock().unwrap().read(path)
|
||||
}
|
||||
|
||||
/// Read a file from the VFS (or from the underlying backend if it isn't
|
||||
/// resident) into a string.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read_to_string`][std::fs::read_to_string].
|
||||
///
|
||||
/// [std::fs::read_to_string]: https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html
|
||||
#[inline]
|
||||
pub fn read_to_string<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<String>> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().read_to_string(path)
|
||||
}
|
||||
|
||||
/// Read a file from the VFS (or the underlying backend if it isn't
|
||||
/// resident) into a string, and normalize its line endings to LF.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read_to_string`][std::fs::read_to_string], but also performs
|
||||
/// line ending normalization.
|
||||
///
|
||||
/// [std::fs::read_to_string]: https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html
|
||||
#[inline]
|
||||
pub fn read_to_string_lf_normalized<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<String>> {
|
||||
let path = path.as_ref();
|
||||
let contents = self.inner.lock().unwrap().read_to_string(path)?;
|
||||
|
||||
Ok(contents.lines().collect::<Vec<&str>>().join("\n").into())
|
||||
}
|
||||
|
||||
/// Write a file to the VFS and the underlying backend.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
||||
|
||||
@@ -1 +1 @@
|
||||
7.4.0
|
||||
7.4.1
|
||||
|
||||
@@ -3,12 +3,12 @@ Error.__index = Error
|
||||
|
||||
Error.Kind = {
|
||||
HttpNotEnabled = {
|
||||
message = "Rojo requires HTTP access, which is not enabled.\n" ..
|
||||
"Check your game settings, located in the 'Home' tab of Studio.",
|
||||
message = "Rojo requires HTTP access, which is not enabled.\n"
|
||||
.. "Check your game settings, located in the 'Home' tab of Studio.",
|
||||
},
|
||||
ConnectFailed = {
|
||||
message = "Couldn't connect to the Rojo server.\n" ..
|
||||
"Make sure the server is running — use 'rojo serve' to run it!",
|
||||
message = "Couldn't connect to the Rojo server.\n"
|
||||
.. "Make sure the server is running — use 'rojo serve' to run it!",
|
||||
},
|
||||
Timeout = {
|
||||
message = "HTTP request timed out.",
|
||||
@@ -63,4 +63,13 @@ function Error.fromRobloxErrorString(message)
|
||||
return Error.new(Error.Kind.Unknown, message)
|
||||
end
|
||||
|
||||
function Error.fromResponse(response)
|
||||
local lower = (response.body or ""):lower()
|
||||
if response.code == 408 or response.code == 504 or lower:find("timed? ?out") then
|
||||
return Error.new(Error.Kind.Timeout)
|
||||
end
|
||||
|
||||
return Error.new(Error.Kind.Unknown, string.format("%s: %s", tostring(response.code), tostring(response.body)))
|
||||
end
|
||||
|
||||
return Error
|
||||
|
||||
@@ -30,8 +30,13 @@ local function performRequest(requestParams)
|
||||
end)
|
||||
|
||||
if success then
|
||||
Log.trace("Request {} success, status code {}", requestId, response.StatusCode)
|
||||
resolve(HttpResponse.fromRobloxResponse(response))
|
||||
Log.trace("Request {} success, response {:#?}", requestId, response)
|
||||
local httpResponse = HttpResponse.fromRobloxResponse(response)
|
||||
if httpResponse:isSuccess() then
|
||||
resolve(httpResponse)
|
||||
else
|
||||
reject(HttpError.fromResponse(httpResponse))
|
||||
end
|
||||
else
|
||||
Log.trace("Request {} failure: {:?}", requestId, response)
|
||||
reject(HttpError.fromRobloxErrorString(response))
|
||||
@@ -63,4 +68,4 @@ function Http.jsonDecode(source)
|
||||
return HttpService:JSONDecode(source)
|
||||
end
|
||||
|
||||
return Http
|
||||
return Http
|
||||
|
||||
@@ -493,9 +493,32 @@ types = {
|
||||
},
|
||||
}
|
||||
|
||||
types.OptionalCFrame = {
|
||||
fromPod = function(pod)
|
||||
if pod == nil then
|
||||
return nil
|
||||
else
|
||||
return types.CFrame.fromPod(pod)
|
||||
end
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
if roblox == nil then
|
||||
return nil
|
||||
else
|
||||
return types.CFrame.toPod(roblox)
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
function EncodedValue.decode(encodedValue)
|
||||
local ty, value = next(encodedValue)
|
||||
|
||||
if ty == nil then
|
||||
-- If the encoded pair is empty, assume it is an unoccupied optional value
|
||||
return true, nil
|
||||
end
|
||||
|
||||
local typeImpl = types[ty]
|
||||
if typeImpl == nil then
|
||||
return false, "Couldn't decode value " .. tostring(ty)
|
||||
|
||||
@@ -370,6 +370,41 @@
|
||||
},
|
||||
"ty": "NumberSequence"
|
||||
},
|
||||
"OptionalCFrame-None": {
|
||||
"value": {
|
||||
"OptionalCFrame": null
|
||||
},
|
||||
"ty": "OptionalCFrame"
|
||||
},
|
||||
"OptionalCFrame-Some": {
|
||||
"value": {
|
||||
"OptionalCFrame": {
|
||||
"position": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"orientation": [
|
||||
[
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "OptionalCFrame"
|
||||
},
|
||||
"PhysicalProperties-Custom": {
|
||||
"value": {
|
||||
"PhysicalProperties": {
|
||||
|
||||
@@ -111,6 +111,18 @@ return {
|
||||
return true, instance:ScaleTo(value)
|
||||
end,
|
||||
},
|
||||
WorldPivotData = {
|
||||
read = function(instance)
|
||||
return true, instance:GetPivot()
|
||||
end,
|
||||
write = function(instance, _, value)
|
||||
if value == nil then
|
||||
return true, nil
|
||||
else
|
||||
return true, instance:PivotTo(value)
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
Terrain = {
|
||||
MaterialColors = {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"Version": [
|
||||
0,
|
||||
607,
|
||||
612,
|
||||
0,
|
||||
6070550
|
||||
6120532
|
||||
],
|
||||
"Classes": {
|
||||
"Accessory": {
|
||||
@@ -345,6 +345,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ActivityHistoryService": {
|
||||
"Name": "ActivityHistoryService",
|
||||
"Tags": [
|
||||
"NotCreatable",
|
||||
"NotReplicated",
|
||||
"Service"
|
||||
],
|
||||
"Superclass": "Instance",
|
||||
"Properties": {},
|
||||
"DefaultProperties": {}
|
||||
},
|
||||
"Actor": {
|
||||
"Name": "Actor",
|
||||
"Tags": [],
|
||||
@@ -11172,10 +11183,12 @@
|
||||
"DataType": {
|
||||
"Value": "CFrame"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"Hidden"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
"Serialization": "DoesNotSerialize"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -11185,10 +11198,12 @@
|
||||
"DataType": {
|
||||
"Value": "Int32"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"Hidden"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
"Serialization": "DoesNotSerialize"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -11198,10 +11213,12 @@
|
||||
"DataType": {
|
||||
"Value": "String"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"Hidden"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
"Serialization": "DoesNotSerialize"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -11211,22 +11228,38 @@
|
||||
"DataType": {
|
||||
"Value": "Int32"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"Hidden"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
"Serialization": "DoesNotSerialize"
|
||||
}
|
||||
}
|
||||
},
|
||||
"IsIdle": {
|
||||
"Name": "IsIdle",
|
||||
"Scriptability": "ReadWrite",
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [
|
||||
"Hidden"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "DoesNotSerialize"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Status": {
|
||||
"Name": "Status",
|
||||
"Scriptability": "None",
|
||||
"Scriptability": "ReadWrite",
|
||||
"DataType": {
|
||||
"Enum": "CollaboratorStatus"
|
||||
},
|
||||
"Tags": [
|
||||
"Hidden",
|
||||
"NotScriptable"
|
||||
"Hidden"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
@@ -11240,10 +11273,12 @@
|
||||
"DataType": {
|
||||
"Value": "Int64"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"Hidden"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
"Serialization": "DoesNotSerialize"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -11253,10 +11288,12 @@
|
||||
"DataType": {
|
||||
"Value": "String"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"Hidden"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
"Serialization": "DoesNotSerialize"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12224,6 +12261,19 @@
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UpDirection": {
|
||||
"Name": "UpDirection",
|
||||
"Scriptability": "ReadWrite",
|
||||
"DataType": {
|
||||
"Value": "Vector3"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultProperties": {
|
||||
@@ -12261,6 +12311,13 @@
|
||||
},
|
||||
"Tags": {
|
||||
"Tags": []
|
||||
},
|
||||
"UpDirection": {
|
||||
"Vector3": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -13286,7 +13343,9 @@
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"Deprecated"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
@@ -15878,6 +15937,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"PermissionPolicy": {
|
||||
"Name": "PermissionPolicy",
|
||||
"Scriptability": "ReadWrite",
|
||||
"DataType": {
|
||||
"Enum": "DragDetectorPermissionPolicy"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PhysicalDragClickedPart": {
|
||||
"Name": "PhysicalDragClickedPart",
|
||||
"Scriptability": "None",
|
||||
@@ -16168,10 +16240,13 @@
|
||||
"Orientation": {
|
||||
"Vector3": [
|
||||
-0.0,
|
||||
180.0,
|
||||
179.99998,
|
||||
90.0
|
||||
]
|
||||
},
|
||||
"PermissionPolicy": {
|
||||
"Enum": 1
|
||||
},
|
||||
"ResponseStyle": {
|
||||
"Enum": 1
|
||||
},
|
||||
@@ -22285,7 +22360,9 @@
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"Deprecated"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
@@ -31232,7 +31309,7 @@
|
||||
},
|
||||
"WorldPivotData": {
|
||||
"Name": "WorldPivotData",
|
||||
"Scriptability": "None",
|
||||
"Scriptability": "Custom",
|
||||
"DataType": {
|
||||
"Value": "OptionalCFrame"
|
||||
},
|
||||
@@ -33071,7 +33148,7 @@
|
||||
},
|
||||
"SerializedDefaultAttributes": {
|
||||
"Name": "SerializedDefaultAttributes",
|
||||
"Scriptability": "Read",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "BinaryString"
|
||||
},
|
||||
@@ -35022,6 +35099,19 @@
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ZIndex": {
|
||||
"Name": "ZIndex",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Int32"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultProperties": {}
|
||||
@@ -35734,6 +35824,58 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DrawConstraintsNetForce": {
|
||||
"Name": "DrawConstraintsNetForce",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DrawContactsNetForce": {
|
||||
"Name": "DrawContactsNetForce",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DrawTotalNetForce": {
|
||||
"Name": "DrawTotalNetForce",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnableForceVisualizationSmoothing": {
|
||||
"Name": "EnableForceVisualizationSmoothing",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"FluidForceDrawScale": {
|
||||
"Name": "FluidForceDrawScale",
|
||||
"Scriptability": "None",
|
||||
@@ -35776,6 +35918,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ForceVisualizationSmoothingSteps": {
|
||||
"Name": "ForceVisualizationSmoothingSteps",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Int32"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"IsInterpolationThrottleShown": {
|
||||
"Name": "IsInterpolationThrottleShown",
|
||||
"Scriptability": "ReadWrite",
|
||||
@@ -35893,6 +36048,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"TorqueDrawScale": {
|
||||
"Name": "TorqueDrawScale",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Float32"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UseCSGv2": {
|
||||
"Name": "UseCSGv2",
|
||||
"Scriptability": "ReadWrite",
|
||||
@@ -38337,6 +38505,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProjectFolderService": {
|
||||
"Name": "ProjectFolderService",
|
||||
"Tags": [
|
||||
"NotCreatable",
|
||||
"NotReplicated",
|
||||
"Service"
|
||||
],
|
||||
"Superclass": "Instance",
|
||||
"Properties": {},
|
||||
"DefaultProperties": {}
|
||||
},
|
||||
"ProximityPrompt": {
|
||||
"Name": "ProximityPrompt",
|
||||
"Tags": [],
|
||||
@@ -40859,6 +41038,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"PreferredUploadId": {
|
||||
"Name": "PreferredUploadId",
|
||||
"Scriptability": "ReadWrite",
|
||||
"DataType": {
|
||||
"Value": "Int64"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RestPose": {
|
||||
"Name": "RestPose",
|
||||
"Scriptability": "ReadWrite",
|
||||
@@ -44505,7 +44697,9 @@
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"Deprecated"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
@@ -46931,21 +47125,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AnimationCompositorMode": {
|
||||
"Name": "AnimationCompositorMode",
|
||||
"Scriptability": "ReadWrite",
|
||||
"DataType": {
|
||||
"Enum": "AnimationCompositorMode"
|
||||
},
|
||||
"Tags": [
|
||||
"NotBrowsable"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AutoJumpEnabled": {
|
||||
"Name": "AutoJumpEnabled",
|
||||
"Scriptability": "ReadWrite",
|
||||
@@ -47488,21 +47667,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"HumanoidStateMachineMode": {
|
||||
"Name": "HumanoidStateMachineMode",
|
||||
"Scriptability": "ReadWrite",
|
||||
"DataType": {
|
||||
"Enum": "HumanoidStateMachineMode"
|
||||
},
|
||||
"Tags": [
|
||||
"NotBrowsable"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoadCharacterAppearance": {
|
||||
"Name": "LoadCharacterAppearance",
|
||||
"Scriptability": "ReadWrite",
|
||||
@@ -47594,9 +47758,6 @@
|
||||
"AllowCustomAnimations": {
|
||||
"Bool": true
|
||||
},
|
||||
"AnimationCompositorMode": {
|
||||
"Enum": 0
|
||||
},
|
||||
"Attributes": {
|
||||
"Attributes": {}
|
||||
},
|
||||
@@ -47729,9 +47890,6 @@
|
||||
"HealthDisplayDistance": {
|
||||
"Float32": 100.0
|
||||
},
|
||||
"HumanoidStateMachineMode": {
|
||||
"Enum": 0
|
||||
},
|
||||
"LoadCharacterAppearance": {
|
||||
"Bool": true
|
||||
},
|
||||
@@ -48619,6 +48777,136 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DraggerActiveColor": {
|
||||
"Name": "DraggerActiveColor",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Color3"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DraggerMajorGridIncrement": {
|
||||
"Name": "DraggerMajorGridIncrement",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Int32"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DraggerMaxSoftSnaps": {
|
||||
"Name": "DraggerMaxSoftSnaps",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Int32"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DraggerPassiveColor": {
|
||||
"Name": "DraggerPassiveColor",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Color3"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DraggerShowHoverRuler": {
|
||||
"Name": "DraggerShowHoverRuler",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DraggerShowMeasurement": {
|
||||
"Name": "DraggerShowMeasurement",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DraggerShowTargetSnap": {
|
||||
"Name": "DraggerShowTargetSnap",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DraggerSoftSnapMarginFactor": {
|
||||
"Name": "DraggerSoftSnapMarginFactor",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Float32"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DraggerSummonMarginFactor": {
|
||||
"Name": "DraggerSummonMarginFactor",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Float32"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DraggerTiltRotateDuration": {
|
||||
"Name": "DraggerTiltRotateDuration",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Float32"
|
||||
},
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Enable Autocomplete": {
|
||||
"Name": "Enable Autocomplete",
|
||||
"Scriptability": "ReadWrite",
|
||||
@@ -55907,6 +56195,17 @@
|
||||
"Properties": {},
|
||||
"DefaultProperties": {}
|
||||
},
|
||||
"TextureGenerationService": {
|
||||
"Name": "TextureGenerationService",
|
||||
"Tags": [
|
||||
"NotCreatable",
|
||||
"NotReplicated",
|
||||
"Service"
|
||||
],
|
||||
"Superclass": "Instance",
|
||||
"Properties": {},
|
||||
"DefaultProperties": {}
|
||||
},
|
||||
"ThirdPartyUserService": {
|
||||
"Name": "ThirdPartyUserService",
|
||||
"Tags": [
|
||||
@@ -59590,6 +59889,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ChatTranslationLocale": {
|
||||
"Name": "ChatTranslationLocale",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "String"
|
||||
},
|
||||
"Tags": [
|
||||
"Hidden",
|
||||
"NotReplicated"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ChatTranslationToggleEnabled": {
|
||||
"Name": "ChatTranslationToggleEnabled",
|
||||
"Scriptability": "None",
|
||||
@@ -59724,6 +60039,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"FramerateCap": {
|
||||
"Name": "FramerateCap",
|
||||
"Scriptability": "None",
|
||||
"DataType": {
|
||||
"Value": "Int32"
|
||||
},
|
||||
"Tags": [
|
||||
"Hidden",
|
||||
"NotReplicated"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Fullscreen": {
|
||||
"Name": "Fullscreen",
|
||||
"Scriptability": "None",
|
||||
@@ -61068,6 +61399,7 @@
|
||||
"VRService": {
|
||||
"Name": "VRService",
|
||||
"Tags": [
|
||||
"NotBrowsable",
|
||||
"NotCreatable",
|
||||
"Service"
|
||||
],
|
||||
@@ -61084,7 +61416,22 @@
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "DoesNotSerialize"
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AvatarGestures": {
|
||||
"Name": "AvatarGestures",
|
||||
"Scriptability": "ReadWrite",
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [
|
||||
"NotBrowsable"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -61111,10 +61458,12 @@
|
||||
"DataType": {
|
||||
"Value": "Bool"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"NotReplicated"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "DoesNotSerialize"
|
||||
"Serialization": "Serializes"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -61124,7 +61473,9 @@
|
||||
"DataType": {
|
||||
"Enum": "UserCFrame"
|
||||
},
|
||||
"Tags": [],
|
||||
"Tags": [
|
||||
"NotReplicated"
|
||||
],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "DoesNotSerialize"
|
||||
@@ -61283,12 +61634,21 @@
|
||||
"Attributes": {
|
||||
"Attributes": {}
|
||||
},
|
||||
"AutomaticScaling": {
|
||||
"Enum": 0
|
||||
},
|
||||
"AvatarGestures": {
|
||||
"Bool": false
|
||||
},
|
||||
"Capabilities": {
|
||||
"SecurityCapabilities": 0
|
||||
},
|
||||
"DefinesCapabilities": {
|
||||
"Bool": false
|
||||
},
|
||||
"FadeOutViewOnCollision": {
|
||||
"Bool": true
|
||||
},
|
||||
"SourceAssetId": {
|
||||
"Int64": -1
|
||||
},
|
||||
@@ -62839,16 +63199,6 @@
|
||||
"Properties": {},
|
||||
"DefaultProperties": {}
|
||||
},
|
||||
"VisibilityService": {
|
||||
"Name": "VisibilityService",
|
||||
"Tags": [
|
||||
"NotCreatable",
|
||||
"Service"
|
||||
],
|
||||
"Superclass": "Instance",
|
||||
"Properties": {},
|
||||
"DefaultProperties": {}
|
||||
},
|
||||
"Visit": {
|
||||
"Name": "Visit",
|
||||
"Tags": [
|
||||
@@ -62933,9 +63283,7 @@
|
||||
"DataType": {
|
||||
"Enum": "AudioApiRollout"
|
||||
},
|
||||
"Tags": [
|
||||
"NotBrowsable"
|
||||
],
|
||||
"Tags": [],
|
||||
"Kind": {
|
||||
"Canonical": {
|
||||
"Serialization": "Serializes"
|
||||
@@ -65214,14 +65562,6 @@
|
||||
"Timeout": 10
|
||||
}
|
||||
},
|
||||
"AnimationCompositorMode": {
|
||||
"name": "AnimationCompositorMode",
|
||||
"items": {
|
||||
"Default": 0,
|
||||
"Disabled": 2,
|
||||
"Enabled": 1
|
||||
}
|
||||
},
|
||||
"AnimationPriority": {
|
||||
"name": "AnimationPriority",
|
||||
"items": {
|
||||
@@ -65835,10 +66175,10 @@
|
||||
"CollaboratorStatus": {
|
||||
"name": "CollaboratorStatus",
|
||||
"items": {
|
||||
"Editing3D": 0,
|
||||
"None": 3,
|
||||
"PrivateScripting": 2,
|
||||
"Scripting": 1
|
||||
"Editing3D": 1,
|
||||
"None": 0,
|
||||
"PrivateScripting": 3,
|
||||
"Scripting": 2
|
||||
}
|
||||
},
|
||||
"CollisionFidelity": {
|
||||
@@ -66307,6 +66647,14 @@
|
||||
"TranslateViewPlane": 4
|
||||
}
|
||||
},
|
||||
"DragDetectorPermissionPolicy": {
|
||||
"name": "DragDetectorPermissionPolicy",
|
||||
"items": {
|
||||
"Everybody": 1,
|
||||
"Nobody": 0,
|
||||
"Scriptable": 2
|
||||
}
|
||||
},
|
||||
"DragDetectorResponseStyle": {
|
||||
"name": "DragDetectorResponseStyle",
|
||||
"items": {
|
||||
@@ -66810,15 +67158,6 @@
|
||||
"R6": 0
|
||||
}
|
||||
},
|
||||
"HumanoidStateMachineMode": {
|
||||
"name": "HumanoidStateMachineMode",
|
||||
"items": {
|
||||
"Default": 0,
|
||||
"Legacy": 1,
|
||||
"LuaStateMachine": 3,
|
||||
"NoStateMachine": 2
|
||||
}
|
||||
},
|
||||
"HumanoidStateType": {
|
||||
"name": "HumanoidStateType",
|
||||
"items": {
|
||||
@@ -67469,6 +67808,16 @@
|
||||
"PersistentPerPlayer": 3
|
||||
}
|
||||
},
|
||||
"ModerationStatus": {
|
||||
"name": "ModerationStatus",
|
||||
"items": {
|
||||
"Invalid": 5,
|
||||
"NotApplicable": 4,
|
||||
"NotReviewed": 3,
|
||||
"ReviewedApproved": 1,
|
||||
"ReviewedRejected": 2
|
||||
}
|
||||
},
|
||||
"ModifierKey": {
|
||||
"name": "ModifierKey",
|
||||
"items": {
|
||||
|
||||
@@ -185,10 +185,10 @@ function ApiContext:write(patch)
|
||||
|
||||
body = Http.jsonEncode(body)
|
||||
|
||||
return Http.post(url, body):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
||||
Log.info("Write response: {:?}", body)
|
||||
return Http.post(url, body):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(responseBody)
|
||||
Log.info("Write response: {:?}", responseBody)
|
||||
|
||||
return body
|
||||
return responseBody
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ function Dropdown:render()
|
||||
self.setContentSize(object.AbsoluteContentSize)
|
||||
end,
|
||||
}),
|
||||
Roact.createFragment(optionButtons),
|
||||
Options = Roact.createFragment(optionButtons),
|
||||
}),
|
||||
})
|
||||
else nil,
|
||||
|
||||
@@ -57,7 +57,7 @@ local function DisplayValue(props)
|
||||
-- We don't need to support mixed tables, so checking the first key is enough
|
||||
-- to determine if it's a simple array
|
||||
local out, i = table.create(#props.value), 0
|
||||
for k, v in props.value do
|
||||
for _, v in props.value do
|
||||
i += 1
|
||||
|
||||
-- Wrap strings in quotes
|
||||
|
||||
@@ -97,21 +97,16 @@ function DomLabel:render()
|
||||
-- Line guides help indent depth remain readable
|
||||
local lineGuides = {}
|
||||
for i = 1, props.depth or 0 do
|
||||
table.insert(
|
||||
lineGuides,
|
||||
e("Frame", {
|
||||
Name = "Line_" .. i,
|
||||
Size = UDim2.new(0, 2, 1, 2),
|
||||
Position = UDim2.new(0, (20 * i) + 15, 0, -1),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = props.transparency,
|
||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||
})
|
||||
)
|
||||
lineGuides["Line_" .. i] = e("Frame", {
|
||||
Size = UDim2.new(0, 2, 1, 2),
|
||||
Position = UDim2.new(0, (20 * i) + 15, 0, -1),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = props.transparency,
|
||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||
})
|
||||
end
|
||||
|
||||
return e("Frame", {
|
||||
Name = "Change",
|
||||
ClipsDescendants = true,
|
||||
BackgroundColor3 = if props.patchType then theme.Diff[props.patchType] else nil,
|
||||
BorderSizePixel = 0,
|
||||
|
||||
@@ -42,7 +42,7 @@ end
|
||||
function TextButton:render()
|
||||
return Theme.with(function(theme)
|
||||
local textSize =
|
||||
TextService:GetTextSize(self.props.text, 18, Enum.Font.GothamSemibold, Vector2.new(math.huge, math.huge))
|
||||
TextService:GetTextSize(self.props.text, 18, Enum.Font.GothamMedium, Vector2.new(math.huge, math.huge))
|
||||
|
||||
local style = self.props.style
|
||||
|
||||
@@ -83,7 +83,7 @@ function TextButton:render()
|
||||
|
||||
Text = e("TextLabel", {
|
||||
Text = self.props.text,
|
||||
Font = Enum.Font.GothamSemibold,
|
||||
Font = Enum.Font.GothamMedium,
|
||||
TextSize = 18,
|
||||
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.TextColor, theme.Disabled.TextColor),
|
||||
TextTransparency = self.props.transparency,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
@@ -19,7 +19,6 @@ local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Packages.Roact)
|
||||
local Log = require(Packages.Log)
|
||||
|
||||
local strict = require(script.Parent.Parent.strict)
|
||||
|
||||
|
||||
@@ -113,27 +113,29 @@ end
|
||||
function InstanceMap:destroyInstance(instance)
|
||||
local id = self.fromInstances[instance]
|
||||
|
||||
local descendants = instance:GetDescendants()
|
||||
instance:Destroy()
|
||||
|
||||
-- After the instance is successfully destroyed,
|
||||
-- we can remove all the id mappings
|
||||
|
||||
if id ~= nil then
|
||||
self:removeId(id)
|
||||
end
|
||||
|
||||
for _, descendantInstance in ipairs(instance:GetDescendants()) do
|
||||
for _, descendantInstance in descendants do
|
||||
self:removeInstance(descendantInstance)
|
||||
end
|
||||
|
||||
instance:Destroy()
|
||||
end
|
||||
|
||||
function InstanceMap:destroyId(id)
|
||||
local instance = self.fromIds[id]
|
||||
self:removeId(id)
|
||||
|
||||
if instance ~= nil then
|
||||
for _, descendantInstance in ipairs(instance:GetDescendants()) do
|
||||
self:removeInstance(descendantInstance)
|
||||
end
|
||||
|
||||
instance:Destroy()
|
||||
self:destroyInstance(instance)
|
||||
else
|
||||
-- There is no instance with this id, so we can just remove the id
|
||||
-- without worrying about instance destruction
|
||||
self:removeId(id)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -426,22 +426,71 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
||||
-- Update isWarning metadata
|
||||
for _, failedChange in unappliedPatch.updated do
|
||||
local node = tree:getNode(failedChange.id)
|
||||
if node then
|
||||
node.isWarning = true
|
||||
Log.trace("Marked node as warning: {} {}", node.id, node.name)
|
||||
|
||||
if node.changeList then
|
||||
for _, change in node.changeList do
|
||||
if failedChange.changedProperties[change[1]] then
|
||||
Log.trace(" Marked property as warning: {}", change[1])
|
||||
if change[4] == nil then
|
||||
change[4] = {}
|
||||
end
|
||||
change[4].isWarning = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if not node then
|
||||
continue
|
||||
end
|
||||
|
||||
node.isWarning = true
|
||||
Log.trace("Marked node as warning: {} {}", node.id, node.name)
|
||||
|
||||
if not node.changeList then
|
||||
continue
|
||||
end
|
||||
for _, change in node.changeList do
|
||||
local property = change[1]
|
||||
local propertyFailedToApply = if property == "Name"
|
||||
then failedChange.changedName ~= nil -- Name is not in changedProperties, so it needs a special case
|
||||
else failedChange.changedProperties[property] ~= nil
|
||||
|
||||
if not propertyFailedToApply then
|
||||
-- This change didn't fail, no need to mark
|
||||
continue
|
||||
end
|
||||
if change[4] == nil then
|
||||
change[4] = { isWarning = true }
|
||||
else
|
||||
change[4].isWarning = true
|
||||
end
|
||||
Log.trace(" Marked property as warning: {}.{}", node.name, property)
|
||||
end
|
||||
end
|
||||
for failedAdditionId in unappliedPatch.added do
|
||||
local node = tree:getNode(failedAdditionId)
|
||||
if not node then
|
||||
continue
|
||||
end
|
||||
|
||||
node.isWarning = true
|
||||
Log.trace("Marked node as warning: {} {}", node.id, node.name)
|
||||
|
||||
if not node.changeList then
|
||||
continue
|
||||
end
|
||||
for _, change in node.changeList do
|
||||
-- Failed addition means that all properties failed to be added
|
||||
if change[4] == nil then
|
||||
change[4] = { isWarning = true }
|
||||
else
|
||||
change[4].isWarning = true
|
||||
end
|
||||
Log.trace(" Marked property as warning: {}.{}", node.name, change[1])
|
||||
end
|
||||
end
|
||||
for _, failedRemovalIdOrInstance in unappliedPatch.removed do
|
||||
local failedRemovalId = if Types.RbxId(failedRemovalIdOrInstance)
|
||||
then failedRemovalIdOrInstance
|
||||
else instanceMap.fromInstances[failedRemovalIdOrInstance]
|
||||
if not failedRemovalId then
|
||||
continue
|
||||
end
|
||||
|
||||
local node = tree:getNode(failedRemovalId)
|
||||
if not node then
|
||||
continue
|
||||
end
|
||||
|
||||
node.isWarning = true
|
||||
Log.trace("Marked node as warning: {} {}", node.id, node.name)
|
||||
end
|
||||
|
||||
-- Update if instances exist
|
||||
|
||||
@@ -25,10 +25,15 @@ local function applyPatch(instanceMap, patch)
|
||||
local unappliedPatch = PatchSet.newEmpty()
|
||||
|
||||
for _, removedIdOrInstance in ipairs(patch.removed) do
|
||||
if Types.RbxId(removedIdOrInstance) then
|
||||
instanceMap:destroyId(removedIdOrInstance)
|
||||
else
|
||||
instanceMap:destroyInstance(removedIdOrInstance)
|
||||
local removeInstanceSuccess = pcall(function()
|
||||
if Types.RbxId(removedIdOrInstance) then
|
||||
instanceMap:destroyId(removedIdOrInstance)
|
||||
else
|
||||
instanceMap:destroyInstance(removedIdOrInstance)
|
||||
end
|
||||
end)
|
||||
if not removeInstanceSuccess then
|
||||
table.insert(unappliedPatch.removed, removedIdOrInstance)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -170,7 +175,13 @@ local function applyPatch(instanceMap, patch)
|
||||
end
|
||||
|
||||
if update.changedName ~= nil then
|
||||
instance.Name = update.changedName
|
||||
local setNameSuccess = pcall(function()
|
||||
instance.Name = update.changedName
|
||||
end)
|
||||
if not setNameSuccess then
|
||||
unappliedUpdate.changedName = update.changedName
|
||||
partiallyApplied = true
|
||||
end
|
||||
end
|
||||
|
||||
if update.changedMetadata ~= nil then
|
||||
@@ -183,15 +194,15 @@ local function applyPatch(instanceMap, patch)
|
||||
|
||||
if update.changedProperties ~= nil then
|
||||
for propertyName, propertyValue in pairs(update.changedProperties) do
|
||||
local ok, decodedValue = decodeValue(propertyValue, instanceMap)
|
||||
if not ok then
|
||||
local decodeSuccess, decodedValue = decodeValue(propertyValue, instanceMap)
|
||||
if not decodeSuccess then
|
||||
unappliedUpdate.changedProperties[propertyName] = propertyValue
|
||||
partiallyApplied = true
|
||||
continue
|
||||
end
|
||||
|
||||
local ok = setProperty(instance, propertyName, decodedValue)
|
||||
if not ok then
|
||||
local setPropertySuccess = setProperty(instance, propertyName, decodedValue)
|
||||
if not setPropertySuccess then
|
||||
unappliedUpdate.changedProperties[propertyName] = propertyValue
|
||||
partiallyApplied = true
|
||||
end
|
||||
|
||||
@@ -27,9 +27,9 @@ local function decodeValue(encodedValue, instanceMap)
|
||||
end
|
||||
end
|
||||
|
||||
local ok, decodedValue = RbxDom.EncodedValue.decode(encodedValue)
|
||||
local decodeSuccess, decodedValue = RbxDom.EncodedValue.decode(encodedValue)
|
||||
|
||||
if not ok then
|
||||
if not decodeSuccess then
|
||||
return false,
|
||||
Error.new(Error.CannotDecodeValue, {
|
||||
encodedValue = encodedValue,
|
||||
|
||||
@@ -147,13 +147,13 @@ local function diff(instanceMap, virtualInstances, rootId)
|
||||
|
||||
local changedProperties = {}
|
||||
for propertyName, virtualValue in pairs(virtualInstance.Properties) do
|
||||
local ok, existingValueOrErr = getProperty(instance, propertyName)
|
||||
local getProperySuccess, existingValueOrErr = getProperty(instance, propertyName)
|
||||
|
||||
if ok then
|
||||
if getProperySuccess then
|
||||
local existingValue = existingValueOrErr
|
||||
local ok, decodedValue = decodeValue(virtualValue, instanceMap)
|
||||
local decodeSuccess, decodedValue = decodeValue(virtualValue, instanceMap)
|
||||
|
||||
if ok then
|
||||
if decodeSuccess then
|
||||
if not trueEquals(existingValue, decodedValue) then
|
||||
Log.debug(
|
||||
"{}.{} changed from '{}' to '{}'",
|
||||
@@ -165,7 +165,6 @@ local function diff(instanceMap, virtualInstances, rootId)
|
||||
changedProperties[propertyName] = virtualValue
|
||||
end
|
||||
else
|
||||
local propertyType = next(virtualValue)
|
||||
Log.warn(
|
||||
"Failed to decode property {}.{}. Encoded property was: {:#?}",
|
||||
virtualInstance.ClassName,
|
||||
@@ -178,10 +177,8 @@ local function diff(instanceMap, virtualInstances, rootId)
|
||||
|
||||
if err.kind == Error.UnknownProperty then
|
||||
Log.trace("Skipping unknown property {}.{}", err.details.className, err.details.propertyName)
|
||||
elseif err.kind == Error.UnreadableProperty then
|
||||
Log.trace("Skipping unreadable property {}.{}", err.details.className, err.details.propertyName)
|
||||
else
|
||||
return false, err
|
||||
Log.trace("Skipping unreadable property {}.{}", err.details.className, err.details.propertyName)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -220,9 +217,9 @@ local function diff(instanceMap, virtualInstances, rootId)
|
||||
table.insert(patch.removed, childInstance)
|
||||
end
|
||||
else
|
||||
local ok, err = diffInternal(childId)
|
||||
local diffSuccess, err = diffInternal(childId)
|
||||
|
||||
if not ok then
|
||||
if not diffSuccess then
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
@@ -243,9 +240,9 @@ local function diff(instanceMap, virtualInstances, rootId)
|
||||
return true
|
||||
end
|
||||
|
||||
local ok, err = diffInternal(rootId)
|
||||
local diffSuccess, err = diffInternal(rootId)
|
||||
|
||||
if not ok then
|
||||
if not diffSuccess then
|
||||
return false, err
|
||||
end
|
||||
|
||||
|
||||
@@ -31,13 +31,13 @@ local function hydrate(instanceMap, virtualInstances, rootId, rootInstance)
|
||||
-- We guard accessing Name and ClassName in order to avoid
|
||||
-- tripping over children of DataModel that Rojo won't have
|
||||
-- permissions to access at all.
|
||||
local ok, name, className = pcall(function()
|
||||
local accessSuccess, name, className = pcall(function()
|
||||
return childInstance.Name, childInstance.ClassName
|
||||
end)
|
||||
|
||||
-- This rule is very conservative and could be loosened in the
|
||||
-- future, or more heuristics could be introduced.
|
||||
if ok and name == virtualChild.Name and className == virtualChild.ClassName then
|
||||
if accessSuccess and name == virtualChild.Name and className == virtualChild.ClassName then
|
||||
isExistingChildVisited[childIndex] = true
|
||||
hydrate(instanceMap, virtualInstances, childId, childInstance)
|
||||
break
|
||||
|
||||
@@ -53,9 +53,9 @@ function reifyInner(instanceMap, virtualInstances, id, parentInstance, unapplied
|
||||
-- Instance.new can fail if we're passing in something that can't be
|
||||
-- created, like a service, something enabled with a feature flag, or
|
||||
-- something that requires higher security than we have.
|
||||
local ok, instance = pcall(Instance.new, virtualInstance.ClassName)
|
||||
local createSuccess, instance = pcall(Instance.new, virtualInstance.ClassName)
|
||||
|
||||
if not ok then
|
||||
if not createSuccess then
|
||||
addAllToPatch(unappliedPatch, virtualInstances, id)
|
||||
return
|
||||
end
|
||||
@@ -80,14 +80,14 @@ function reifyInner(instanceMap, virtualInstances, id, parentInstance, unapplied
|
||||
continue
|
||||
end
|
||||
|
||||
local ok, value = decodeValue(virtualValue, instanceMap)
|
||||
if not ok then
|
||||
local decodeSuccess, value = decodeValue(virtualValue, instanceMap)
|
||||
if not decodeSuccess then
|
||||
unappliedProperties[propertyName] = virtualValue
|
||||
continue
|
||||
end
|
||||
|
||||
local ok = setProperty(instance, propertyName, value)
|
||||
if not ok then
|
||||
local setPropertySuccess = setProperty(instance, propertyName, value)
|
||||
if not setPropertySuccess then
|
||||
unappliedProperties[propertyName] = virtualValue
|
||||
end
|
||||
end
|
||||
@@ -148,8 +148,8 @@ function applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||
continue
|
||||
end
|
||||
|
||||
local ok = setProperty(entry.instance, entry.propertyName, targetInstance)
|
||||
if not ok then
|
||||
local setPropertySuccess = setProperty(entry.instance, entry.propertyName, targetInstance)
|
||||
if not setPropertySuccess then
|
||||
markFailed(entry.id, entry.propertyName, entry.virtualValue)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,6 @@ return function()
|
||||
|
||||
local PatchSet = require(script.Parent.Parent.PatchSet)
|
||||
local InstanceMap = require(script.Parent.Parent.InstanceMap)
|
||||
local Error = require(script.Parent.Error)
|
||||
|
||||
local function isEmpty(table)
|
||||
return next(table) == nil, "Table was not empty"
|
||||
|
||||
@@ -28,9 +28,9 @@ local function setProperty(instance, propertyName, value)
|
||||
})
|
||||
end
|
||||
|
||||
local ok, err = descriptor:write(instance, value)
|
||||
local writeSuccess, err = descriptor:write(instance, value)
|
||||
|
||||
if not ok then
|
||||
if not writeSuccess then
|
||||
if err.kind == RbxDom.Error.Kind.Roblox and err.extra:find("lacking permission") then
|
||||
return false,
|
||||
Error.new(Error.LackingPropertyPermissions, {
|
||||
|
||||
@@ -21,8 +21,8 @@ local Status = strict("Session.Status", {
|
||||
Disconnected = "Disconnected",
|
||||
})
|
||||
|
||||
local function debugPatch(patch)
|
||||
return Fmt.debugify(patch, function(patch, output)
|
||||
local function debugPatch(object)
|
||||
return Fmt.debugify(object, function(patch, output)
|
||||
output:writeLine("Patch {{")
|
||||
output:indent()
|
||||
|
||||
@@ -197,7 +197,7 @@ function ServeSession:__onActiveScriptChanged(activeScript)
|
||||
local existingParent = activeScript.Parent
|
||||
activeScript.Parent = nil
|
||||
|
||||
for i = 1, 3 do
|
||||
for _ = 1, 3 do
|
||||
RunService.Heartbeat:Wait()
|
||||
end
|
||||
|
||||
@@ -251,7 +251,10 @@ function ServeSession:__initialSync(serverInfo)
|
||||
|
||||
if userDecision == "Abort" then
|
||||
return Promise.reject("Aborted Rojo sync operation")
|
||||
elseif userDecision == "Reject" and self.__twoWaySync then
|
||||
elseif userDecision == "Reject" then
|
||||
if not self.__twoWaySync then
|
||||
return Promise.reject("Cannot reject sync operation without two-way sync enabled")
|
||||
end
|
||||
-- The user wants their studio DOM to write back to their Rojo DOM
|
||||
-- so we will reverse the patch and send it back
|
||||
|
||||
@@ -268,7 +271,7 @@ function ServeSession:__initialSync(serverInfo)
|
||||
table.insert(inversePatch.updated, update)
|
||||
end
|
||||
-- Add the removed instances back to Rojo
|
||||
-- selene:allow(empty_if, unused_variable)
|
||||
-- selene:allow(empty_if, unused_variable, empty_loop)
|
||||
for _, instance in catchUpPatch.removed do
|
||||
-- TODO: Generate ID for our instance and add it to inversePatch.added
|
||||
end
|
||||
@@ -277,7 +280,7 @@ function ServeSession:__initialSync(serverInfo)
|
||||
table.insert(inversePatch.removed, id)
|
||||
end
|
||||
|
||||
self.__apiContext:write(inversePatch)
|
||||
return self.__apiContext:write(inversePatch)
|
||||
elseif userDecision == "Accept" then
|
||||
local unappliedPatch = self.__reconciler:applyPatch(catchUpPatch)
|
||||
|
||||
@@ -287,6 +290,10 @@ function ServeSession:__initialSync(serverInfo)
|
||||
PatchSet.humanSummary(self.__instanceMap, unappliedPatch)
|
||||
)
|
||||
end
|
||||
|
||||
return Promise.resolve()
|
||||
else
|
||||
return Promise.reject("Invalid user decision: " .. userDecision)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
--[[
|
||||
Create a new signal that can be connected to, disconnected from, and fired.
|
||||
|
||||
Usage:
|
||||
|
||||
local signal = createSignal()
|
||||
local disconnect = signal:connect(function(...)
|
||||
print("fired:", ...)
|
||||
end)
|
||||
|
||||
signal:fire("a", "b", "c")
|
||||
disconnect()
|
||||
|
||||
Avoids mutating listeners list directly to prevent iterator invalidation if
|
||||
a listener is disconnected while the signal is firing.
|
||||
]]
|
||||
local function createSignal()
|
||||
local listeners = {}
|
||||
|
||||
local function connect(newListener)
|
||||
local nextListeners = {}
|
||||
for listener in pairs(listeners) do
|
||||
nextListeners[listener] = true
|
||||
end
|
||||
|
||||
nextListeners[newListener] = true
|
||||
listeners = nextListeners
|
||||
|
||||
return function()
|
||||
local nextListeners = {}
|
||||
for listener in pairs(listeners) do
|
||||
if listener ~= newListener then
|
||||
nextListeners[listener] = true
|
||||
end
|
||||
end
|
||||
|
||||
listeners = nextListeners
|
||||
end
|
||||
end
|
||||
|
||||
local function fire(...)
|
||||
for listener in pairs(listeners) do
|
||||
listener(...)
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
connect = connect,
|
||||
fire = fire,
|
||||
}
|
||||
end
|
||||
|
||||
return createSignal
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: tests/tests/build.rs
|
||||
expression: contents
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="Folder" referent="0">
|
||||
<Properties>
|
||||
<string name="Name">top-level</string>
|
||||
</Properties>
|
||||
<Item class="Folder" referent="1">
|
||||
<Properties>
|
||||
<string name="Name">second-level</string>
|
||||
</Properties>
|
||||
<Item class="IntValue" referent="2">
|
||||
<Properties>
|
||||
<string name="Name">third-level</string>
|
||||
<int64 name="Value">1337</int64>
|
||||
</Properties>
|
||||
</Item>
|
||||
</Item>
|
||||
</Item>
|
||||
</roblox>
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: tests/tests/build.rs
|
||||
expression: contents
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="Folder" referent="0">
|
||||
<Properties>
|
||||
<string name="Name">no_name_project</string>
|
||||
</Properties>
|
||||
<Item class="Folder" referent="1">
|
||||
<Properties>
|
||||
<string name="Name">second-level</string>
|
||||
</Properties>
|
||||
<Item class="BoolValue" referent="2">
|
||||
<Properties>
|
||||
<string name="Name">bool_value</string>
|
||||
<bool name="Value">true</bool>
|
||||
</Properties>
|
||||
</Item>
|
||||
</Item>
|
||||
</Item>
|
||||
</roblox>
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: tests/tests/build.rs
|
||||
assertion_line: 104
|
||||
expression: contents
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="StringValue" referent="0">
|
||||
<Properties>
|
||||
<string name="Name">no_name_top_level_project</string>
|
||||
<string name="Value">If this isn't named `no_name_top_level_project`, something went wrong!</string>
|
||||
</Properties>
|
||||
</Item>
|
||||
</roblox>
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: tests/tests/build.rs
|
||||
expression: contents
|
||||
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="Folder" referent="0">
|
||||
@@ -25,6 +24,7 @@ expression: contents
|
||||
<R21>0</R21>
|
||||
<R22>1</R22>
|
||||
</CoordinateFrame>
|
||||
<bool name="NeedsPivotMigration">false</bool>
|
||||
<Ref name="PrimaryPart">null</Ref>
|
||||
<BinaryString name="Tags"></BinaryString>
|
||||
</Properties>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: tests/tests/build.rs
|
||||
expression: contents
|
||||
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="DataModel" referent="0">
|
||||
@@ -22,6 +21,7 @@ expression: contents
|
||||
<Item class="Workspace" referent="2">
|
||||
<Properties>
|
||||
<string name="Name">Workspace</string>
|
||||
<bool name="NeedsPivotMigration">false</bool>
|
||||
</Properties>
|
||||
<Item class="BoolValue" referent="3">
|
||||
<Properties>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "top-level",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"second-level": {
|
||||
"$path": "src"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"tree": {
|
||||
"$className": "IntValue",
|
||||
"$properties": {
|
||||
"Value": 1337
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "no_name_project",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"second-level": {
|
||||
"$path": "src"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"tree": {
|
||||
"$className": "BoolValue",
|
||||
"$properties": {
|
||||
"Value": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"tree": {
|
||||
"$className": "StringValue",
|
||||
"$properties": {
|
||||
"Value": "If this isn't named `no_name_top_level_project`, something went wrong!"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||
|
||||
---
|
||||
instances:
|
||||
id-2:
|
||||
@@ -22,7 +21,9 @@ instances:
|
||||
ignoreUnknownInstances: false
|
||||
Name: test
|
||||
Parent: id-2
|
||||
Properties: {}
|
||||
Properties:
|
||||
NeedsPivotMigration:
|
||||
Bool: false
|
||||
messageCursor: 1
|
||||
sessionId: id-1
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
|
||||
|
||||
---
|
||||
messageCursor: 1
|
||||
messages:
|
||||
@@ -14,7 +13,9 @@ messages:
|
||||
ignoreUnknownInstances: false
|
||||
Name: test
|
||||
Parent: id-2
|
||||
Properties: {}
|
||||
Properties:
|
||||
NeedsPivotMigration:
|
||||
Bool: false
|
||||
removed: []
|
||||
updated: []
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||
---
|
||||
instances:
|
||||
id-2:
|
||||
Children:
|
||||
- id-3
|
||||
ClassName: Folder
|
||||
Id: id-2
|
||||
Metadata:
|
||||
ignoreUnknownInstances: true
|
||||
Name: top-level
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
id-3:
|
||||
Children:
|
||||
- id-4
|
||||
ClassName: Folder
|
||||
Id: id-3
|
||||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: second-level
|
||||
Parent: id-2
|
||||
Properties: {}
|
||||
id-4:
|
||||
Children: []
|
||||
ClassName: IntValue
|
||||
Id: id-4
|
||||
Metadata:
|
||||
ignoreUnknownInstances: true
|
||||
Name: third-level
|
||||
Parent: id-3
|
||||
Properties:
|
||||
Value:
|
||||
Int64: 1337
|
||||
messageCursor: 0
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
assertion_line: 316
|
||||
expression: redactions.redacted_yaml(info)
|
||||
---
|
||||
expectedPlaceIds: ~
|
||||
gameId: ~
|
||||
placeId: ~
|
||||
projectName: top-level
|
||||
protocolVersion: 4
|
||||
rootInstanceId: id-2
|
||||
serverVersion: "[server-version]"
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
assertion_line: 338
|
||||
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||
---
|
||||
instances:
|
||||
id-2:
|
||||
Children:
|
||||
- id-3
|
||||
ClassName: Folder
|
||||
Id: id-2
|
||||
Metadata:
|
||||
ignoreUnknownInstances: true
|
||||
Name: no_name_project
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
id-3:
|
||||
Children:
|
||||
- id-4
|
||||
ClassName: Folder
|
||||
Id: id-3
|
||||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: second-level
|
||||
Parent: id-2
|
||||
Properties: {}
|
||||
id-4:
|
||||
Children: []
|
||||
ClassName: BoolValue
|
||||
Id: id-4
|
||||
Metadata:
|
||||
ignoreUnknownInstances: true
|
||||
Name: bool_value
|
||||
Parent: id-3
|
||||
Properties:
|
||||
Value:
|
||||
Bool: true
|
||||
messageCursor: 0
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
assertion_line: 335
|
||||
expression: redactions.redacted_yaml(info)
|
||||
---
|
||||
expectedPlaceIds: ~
|
||||
gameId: ~
|
||||
placeId: ~
|
||||
projectName: no_name_project
|
||||
protocolVersion: 4
|
||||
rootInstanceId: id-2
|
||||
serverVersion: "[server-version]"
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
assertion_line: 306
|
||||
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||
---
|
||||
instances:
|
||||
id-2:
|
||||
Children: []
|
||||
ClassName: StringValue
|
||||
Id: id-2
|
||||
Metadata:
|
||||
ignoreUnknownInstances: true
|
||||
Name: no_name_top_level_project
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties:
|
||||
Value:
|
||||
String: "If this isn't named `no_name_top_level_project`, something went wrong!"
|
||||
messageCursor: 0
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
assertion_line: 300
|
||||
expression: redactions.redacted_yaml(info)
|
||||
---
|
||||
expectedPlaceIds: ~
|
||||
gameId: ~
|
||||
placeId: ~
|
||||
projectName: no_name_top_level_project
|
||||
protocolVersion: 4
|
||||
rootInstanceId: id-2
|
||||
serverVersion: "[server-version]"
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "top-level",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"second-level": {
|
||||
"$path": "src"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"tree": {
|
||||
"$className": "IntValue",
|
||||
"$properties": {
|
||||
"Value": 1337
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "no_name_project",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"second-level": {
|
||||
"$path": "src"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"tree": {
|
||||
"$className": "BoolValue",
|
||||
"$properties": {
|
||||
"Value": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"tree": {
|
||||
"$className": "StringValue",
|
||||
"$properties": {
|
||||
"Value": "If this isn't named `no_name_top_level_project`, something went wrong!"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ enum Error {
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct Project {
|
||||
/// The name of the top-level instance described by the project.
|
||||
pub name: String,
|
||||
pub name: Option<String>,
|
||||
|
||||
/// The tree of instances described by this project. Projects always
|
||||
/// describe at least one instance.
|
||||
|
||||
@@ -110,7 +110,7 @@ impl ServeSession {
|
||||
|
||||
log::debug!("Loading project file from {}", project_path.display());
|
||||
|
||||
let root_project = match vfs.read(&project_path).with_not_found()? {
|
||||
let mut root_project = match vfs.read(&project_path).with_not_found()? {
|
||||
Some(contents) => Project::load_from_slice(&contents, &project_path)?,
|
||||
None => {
|
||||
return Err(ServeSessionError::NoProjectFound {
|
||||
@@ -118,6 +118,35 @@ impl ServeSession {
|
||||
});
|
||||
}
|
||||
};
|
||||
if root_project.name.is_none() {
|
||||
if let Some(file_name) = project_path.file_name().and_then(|s| s.to_str()) {
|
||||
if file_name == "default.project.json" {
|
||||
let folder_name = project_path
|
||||
.parent()
|
||||
.and_then(Path::file_name)
|
||||
.and_then(|s| s.to_str());
|
||||
if let Some(folder_name) = folder_name {
|
||||
root_project.name = Some(folder_name.to_string());
|
||||
} else {
|
||||
return Err(ServeSessionError::FolderNameInvalid {
|
||||
path: project_path.to_path_buf(),
|
||||
});
|
||||
}
|
||||
} else if let Some(trimmed) = file_name.strip_suffix(".project.json") {
|
||||
root_project.name = Some(trimmed.to_string());
|
||||
} else {
|
||||
return Err(ServeSessionError::ProjectNameInvalid {
|
||||
path: project_path.to_path_buf(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Err(ServeSessionError::ProjectNameInvalid {
|
||||
path: project_path.to_path_buf(),
|
||||
});
|
||||
}
|
||||
}
|
||||
// Rebind it to make it no longer mutable
|
||||
let root_project = root_project;
|
||||
|
||||
let mut tree = RojoTree::new(InstanceSnapshot::new());
|
||||
|
||||
@@ -190,7 +219,10 @@ impl ServeSession {
|
||||
}
|
||||
|
||||
pub fn project_name(&self) -> &str {
|
||||
&self.root_project.name
|
||||
self.root_project
|
||||
.name
|
||||
.as_ref()
|
||||
.expect("all top-level projects must have their name set")
|
||||
}
|
||||
|
||||
pub fn project_port(&self) -> Option<u16> {
|
||||
@@ -231,6 +263,14 @@ pub enum ServeSessionError {
|
||||
)]
|
||||
NoProjectFound { path: PathBuf },
|
||||
|
||||
#[error("The folder for the provided project cannot be used as a project name: {}\n\
|
||||
Consider setting the `name` field on this project.", .path.display())]
|
||||
FolderNameInvalid { path: PathBuf },
|
||||
|
||||
#[error("The file name of the provided project cannot be used as a project name: {}.\n\
|
||||
Consider setting the `name` field on this project.", .path.display())]
|
||||
ProjectNameInvalid { path: PathBuf },
|
||||
|
||||
#[error(transparent)]
|
||||
Io {
|
||||
#[from]
|
||||
|
||||
@@ -87,10 +87,28 @@ impl RojoTree {
|
||||
}
|
||||
|
||||
pub fn insert_instance(&mut self, parent_ref: Ref, snapshot: InstanceSnapshot) -> Ref {
|
||||
// !!!!!!!!!! UGLY HACK !!!!!!!!!!
|
||||
//
|
||||
// This is a set of special cases working around a more general problem upstream
|
||||
// in rbx-dom that causes pivots to not build to file correctly, described in
|
||||
// github.com/rojo-rbx/rojo/issues/628.
|
||||
//
|
||||
// We need to insert the NeedsPivotMigration property with a value of false on
|
||||
// every instance that inherits from Model for pivots to build correctly.
|
||||
let hack_needs_pivot_migration = match snapshot.class_name.as_ref() {
|
||||
"Model" | "Actor" | "Tool" | "HopperBin" | "Flag" | "WorldModel" | "Workspace"
|
||||
if !snapshot.properties.contains_key("NeedsPivotMigration") =>
|
||||
{
|
||||
vec![("NeedsPivotMigration", Variant::Bool(false))]
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
let builder = InstanceBuilder::empty()
|
||||
.with_class(snapshot.class_name.into_owned())
|
||||
.with_name(snapshot.name.into_owned())
|
||||
.with_properties(snapshot.properties);
|
||||
.with_properties(snapshot.properties)
|
||||
.with_properties(hack_needs_pivot_migration);
|
||||
|
||||
let referent = self.inner.insert(parent_ref, builder);
|
||||
self.insert_metadata(referent, snapshot.metadata);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{collections::HashMap, path::Path, str};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use anyhow::Context;
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
use rbx_dom_weak::types::Enum;
|
||||
|
||||
@@ -58,10 +57,8 @@ pub fn snapshot_lua(
|
||||
(_, ScriptType::Module) => ("ModuleScript", None),
|
||||
};
|
||||
|
||||
let contents = vfs.read(path)?;
|
||||
let contents_str = str::from_utf8(&contents)
|
||||
.with_context(|| format!("File was not valid UTF-8: {}", path.display()))?
|
||||
.to_owned();
|
||||
let contents = vfs.read_to_string_lf_normalized(path)?;
|
||||
let contents_str = contents.as_str();
|
||||
|
||||
let mut properties = HashMap::with_capacity(2);
|
||||
properties.insert("Source".to_owned(), contents_str.into());
|
||||
|
||||
@@ -22,6 +22,25 @@ pub fn snapshot_project(
|
||||
let project = Project::load_from_slice(&vfs.read(path)?, path)
|
||||
.with_context(|| format!("File was not a valid Rojo project: {}", path.display()))?;
|
||||
|
||||
// This is not how I would normally do this, but this is a temporary
|
||||
// implementation. The one in 7.5+ is better.
|
||||
let project_name = project.name.as_deref().unwrap_or_else(|| {
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.expect("project file names should be valid UTF-8");
|
||||
if file_name == "default.project.json" {
|
||||
path.parent()
|
||||
.and_then(Path::file_name)
|
||||
.and_then(|s| s.to_str())
|
||||
.expect("default.project.json should be inside a folder with a valid UTF-8 name")
|
||||
} else {
|
||||
file_name
|
||||
.strip_suffix(".project.json")
|
||||
.expect("project file names should end with .project.json")
|
||||
}
|
||||
});
|
||||
|
||||
let mut context = context.clone();
|
||||
|
||||
let rules = project.glob_ignore_paths.iter().map(|glob| PathIgnoreRule {
|
||||
@@ -37,7 +56,7 @@ pub fn snapshot_project(
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
match snapshot_project_node(&context, path, &project.name, &project.tree, vfs, None)? {
|
||||
match snapshot_project_node(&context, path, project_name, &project.tree, vfs, None)? {
|
||||
Some(found_snapshot) => {
|
||||
let mut snapshot = found_snapshot;
|
||||
// Setting the instigating source to the project file path is a little
|
||||
@@ -669,4 +688,36 @@ mod test {
|
||||
|
||||
insta::assert_yaml_snapshot!(instance_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_name_project() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let mut imfs = InMemoryFs::new();
|
||||
imfs.load_snapshot(
|
||||
"/no_name_project",
|
||||
VfsSnapshot::dir(hashmap! {
|
||||
"default.project.json" => VfsSnapshot::file(r#"
|
||||
{
|
||||
"tree": {
|
||||
"$className": "Model"
|
||||
}
|
||||
}
|
||||
"#),
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let vfs = Vfs::new(imfs);
|
||||
|
||||
let instance_snapshot = snapshot_project(
|
||||
&InstanceContext::default(),
|
||||
&vfs,
|
||||
Path::new("/no_name_project/default.project.json"),
|
||||
)
|
||||
.expect("snapshot error")
|
||||
.expect("snapshot returned no instances");
|
||||
|
||||
insta::assert_yaml_snapshot!(instance_snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
source: src/snapshot_middleware/project.rs
|
||||
expression: instance_snapshot
|
||||
---
|
||||
snapshot_id: "00000000000000000000000000000000"
|
||||
metadata:
|
||||
ignore_unknown_instances: true
|
||||
instigating_source:
|
||||
Path: /no_name_project/default.project.json
|
||||
relevant_paths:
|
||||
- /no_name_project/default.project.json
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
name: no_name_project
|
||||
class_name: Model
|
||||
properties: {}
|
||||
children: []
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{path::Path, str};
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
use maplit::hashmap;
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
|
||||
@@ -14,11 +13,8 @@ pub fn snapshot_txt(
|
||||
path: &Path,
|
||||
) -> anyhow::Result<Option<InstanceSnapshot>> {
|
||||
let name = path.file_name_trim_end(".txt")?;
|
||||
|
||||
let contents = vfs.read(path)?;
|
||||
let contents_str = str::from_utf8(&contents)
|
||||
.with_context(|| format!("File was not valid UTF-8: {}", path.display()))?
|
||||
.to_owned();
|
||||
let contents = vfs.read_to_string(path)?;
|
||||
let contents_str = contents.as_str();
|
||||
|
||||
let properties = hashmap! {
|
||||
"Value".to_owned() => contents_str.into(),
|
||||
|
||||
@@ -59,6 +59,9 @@ gen_build_tests! {
|
||||
txt_in_folder,
|
||||
unresolved_values,
|
||||
weldconstraint,
|
||||
no_name_default_project,
|
||||
no_name_project,
|
||||
no_name_top_level_project,
|
||||
}
|
||||
|
||||
fn run_build_test(test_name: &str) {
|
||||
@@ -70,7 +73,7 @@ fn run_build_test(test_name: &str) {
|
||||
let output_path = output_dir.path().join(format!("{}.rbxmx", test_name));
|
||||
|
||||
let output = Command::new(ROJO_PATH)
|
||||
.args(&[
|
||||
.args([
|
||||
"build",
|
||||
input_path.to_str().unwrap(),
|
||||
"-o",
|
||||
|
||||
@@ -255,3 +255,57 @@ fn add_optional_folder() {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_name_default_project() {
|
||||
run_serve_test("no_name_default_project", |session, mut redactions| {
|
||||
let info = session.get_api_rojo().unwrap();
|
||||
let root_id = info.root_instance_id;
|
||||
|
||||
assert_yaml_snapshot!(
|
||||
"no_name_default_project_info",
|
||||
redactions.redacted_yaml(info)
|
||||
);
|
||||
|
||||
let read_response = session.get_api_read(root_id).unwrap();
|
||||
assert_yaml_snapshot!(
|
||||
"no_name_default_project_all",
|
||||
read_response.intern_and_redact(&mut redactions, root_id)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_name_project() {
|
||||
run_serve_test("no_name_project", |session, mut redactions| {
|
||||
let info = session.get_api_rojo().unwrap();
|
||||
let root_id = info.root_instance_id;
|
||||
|
||||
assert_yaml_snapshot!("no_name_project_info", redactions.redacted_yaml(info));
|
||||
|
||||
let read_response = session.get_api_read(root_id).unwrap();
|
||||
assert_yaml_snapshot!(
|
||||
"no_name_project_all",
|
||||
read_response.intern_and_redact(&mut redactions, root_id)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_name_top_level_project() {
|
||||
run_serve_test("no_name_top_level_project", |session, mut redactions| {
|
||||
let info = session.get_api_rojo().unwrap();
|
||||
let root_id = info.root_instance_id;
|
||||
|
||||
assert_yaml_snapshot!(
|
||||
"no_name_top_level_project_info",
|
||||
redactions.redacted_yaml(info)
|
||||
);
|
||||
|
||||
let read_response = session.get_api_read(root_id).unwrap();
|
||||
assert_yaml_snapshot!(
|
||||
"no_name_top_level_project_all",
|
||||
read_response.intern_and_redact(&mut redactions, root_id)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user