Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify usage of attributes. #574

Merged
merged 15 commits into from
Aug 4, 2022
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: tests/tests/build.rs
assertion_line: 99
expression: contents
---
<roblox version="4">
Expand All @@ -11,12 +10,24 @@ expression: contents
<Item class="Folder" referent="1">
<Properties>
<string name="Name">Explicit</string>
<BinaryString name="AttributesSerialize">AgAAAAUAAABIZWxsbwIFAAAAV29ybGQGAAAAVmVjdG9yEQAAgD8AAABAAABAQA==</BinaryString>
<BinaryString name="AttributesSerialize">DQAAAAQAAABCb29sAwEKAAAAQnJpY2tDb2xvcg4BAAAABgAAAENvbG9yMw8AAAAAAAAAAAAAAAANAAAAQ29sb3JTZXF1ZW5jZRkCAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAACAPwAAgD8AAIA/AACAPwcAAABGbG9hdDMyBQAAAAAHAAAARmxvYXQ2NAYAAAAAAAAAAAsAAABOdW1iZXJSYW5nZRsAAAAAAAAAAA4AAABOdW1iZXJTZXF1ZW5jZRcCAAAAAAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAABAAAAFJlY3QcAAAAAAAAAAAAAAAAAAAAAAQAAABVRGltCQAAAAAAAAAABQAAAFVEaW0yCgAAAAAAAAAAAAAAAAAAAAAHAAAAVmVjdG9yMhAAAAAAAAAAAAcAAABWZWN0b3IzEQAAAAAAAAAAAAAAAA==</BinaryString>
</Properties>
</Item>
<Item class="Folder" referent="2">
<Properties>
<string name="Name">ImplicitAttributes</string>
<string name="Name">Implicit</string>
<BinaryString name="AttributesSerialize">AwAAAAQAAABCb29sAwEGAAAATnVtYmVyBgAAAAAAAOA/BgAAAFN0cmluZwIEAAAAVGVzdA==</BinaryString>
</Properties>
</Item>
<Item class="Folder" referent="3">
<Properties>
<string name="Name">LegacyExplicit</string>
<BinaryString name="AttributesSerialize">AgAAAAUAAABIZWxsbwIFAAAAV29ybGQGAAAAVmVjdG9yEQAAgD8AAABAAABAQA==</BinaryString>
</Properties>
</Item>
<Item class="Folder" referent="4">
<Properties>
<string name="Name">LegacyImplicit</string>
<BinaryString name="AttributesSerialize">AgAAAAMAAABIZXkCBwAAAEdyYW5kbWEGAAAAVmVjdG9yEQAAgEAAAKBAAADAQA==</BinaryString>
</Properties>
</Item>
Expand Down
80 changes: 79 additions & 1 deletion rojo-test/build-tests/attributes/default.project.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,85 @@
"tree": {
"$className": "Folder",

"Implicit": {
"$className": "Folder",
"$attributes": {
"Bool": true,
"Number": 0.5,
"String": "Test"
}
},

"Explicit": {
"$className": "Folder",
"$attributes": {
"Bool": {
"Bool": true
},
"Float32": {
"Float32": 0
},
"Float64": {
"Float64": 0
},
"UDim": {
"UDim": [0, 0]
},
"UDim2": {
"UDim2": [[0, 0], [0, 0]]
},
"BrickColor": {
"BrickColor": 1
},
"Color3": {
"Color3": [0, 0, 0]
},
"Vector2": {
"Vector2": [0, 0]
},
"Vector3": {
"Vector3": [0, 0, 0]
},
"NumberSequence": {
"NumberSequence": {
"keypoints": [
{
"time": 0,
"value": 0,
"envelope": 0
},
{
"time": 1,
"value": 0,
"envelope": 0
}
]
}
},
"ColorSequence": {
"ColorSequence": {
"keypoints": [
{
"time": 0,
"color": [1, 1, 1]
},
{
"time": 1,
"color": [1, 1, 1]
}
]
}
},
"NumberRange": {
"NumberRange": [0, 0]
},
"Rect": {
"Rect": [[0, 0], [0, 0]]
}
}
},

"LegacyExplicit": {
"$className": "Folder",
"$properties": {
"Attributes": {
Expand All @@ -19,7 +97,7 @@
}
},

"ImplicitAttributes": {
"LegacyImplicit": {
"$className": "Folder",
"$properties": {
"Attributes": {
Expand Down
7 changes: 7 additions & 0 deletions src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ pub struct ProjectNode {
)]
pub properties: HashMap<String, UnresolvedValue>,

#[serde(
rename = "$attributes",
default,
skip_serializing_if = "HashMap::is_empty"
)]
pub attributes: HashMap<String, UnresolvedValue>,

/// Defines the behavior when Rojo encounters unknown instances in Roblox
/// Studio during live sync. `$ignoreUnknownInstances` should be considered
/// a large hammer and used with care.
Expand Down
36 changes: 34 additions & 2 deletions src/resolution.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::borrow::Borrow;

use anyhow::format_err;
use anyhow::{bail, format_err};
use rbx_dom_weak::types::{
Attributes, CFrame, Color3, Content, Enum, Matrix3, Tags, Variant, VariantType, Vector2,
Vector3,
Expand Down Expand Up @@ -28,6 +28,13 @@ impl UnresolvedValue {
UnresolvedValue::Ambiguous(partial) => partial.resolve(class_name, prop_name),
}
}

pub fn resolve_unambiguous(self) -> anyhow::Result<Variant> {
match self {
UnresolvedValue::FullyQualified(full) => Ok(full),
UnresolvedValue::Ambiguous(partial) => partial.resolve_unambiguous(),
}
}
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -148,6 +155,16 @@ impl AmbiguousValue {
}
}

pub fn resolve_unambiguous(self) -> anyhow::Result<Variant> {
match self {
AmbiguousValue::Bool(value) => Ok(value.into()),
AmbiguousValue::Number(value) => Ok(value.into()),
AmbiguousValue::String(value) => Ok(value.into()),

other => bail!("Cannot unambiguously resolve the value {other:?}"),
}
}

fn describe(&self) -> &'static str {
match self {
AmbiguousValue::Bool(_) => "a bool",
Expand Down Expand Up @@ -218,12 +235,20 @@ mod test {
unresolved.resolve(class, prop).unwrap()
}

fn resolve_unambiguous(json_value: &str) -> Variant {
let unresolved: UnresolvedValue = serde_json::from_str(json_value).unwrap();
unresolved.resolve_unambiguous().unwrap()
}

#[test]
fn bools() {
assert_eq!(resolve("BoolValue", "Value", "false"), Variant::Bool(false));

// Script.Disabled is inherited from BaseScript
assert_eq!(resolve("Script", "Disabled", "true"), Variant::Bool(true));

assert_eq!(resolve_unambiguous("false"), Variant::Bool(false));
assert_eq!(resolve_unambiguous("true"), Variant::Bool(true));
}

#[test]
Expand All @@ -247,6 +272,11 @@ mod test {
// resolve("Folder", "Tags", "\"a\\u0000b\\u0000c\""),
// Variant::BinaryString(b"a\0b\0c".to_vec().into()),
// );

assert_eq!(
resolve_unambiguous("\"Hello world!\""),
Variant::String("Hello world!".into()),
);
}

#[test]
Expand All @@ -257,12 +287,14 @@ mod test {
);

assert_eq!(
resolve("Folder", "SourceAssetId", "532413"),
resolve("IntValue", "Value", "532413"),
Variant::Int64(532413),
);

assert_eq!(resolve("Part", "Transparency", "1"), Variant::Float32(1.0));
assert_eq!(resolve("NumberValue", "Value", "1"), Variant::Float64(1.0));

assert_eq!(resolve_unambiguous("12.5"), Variant::Float64(12.5));
}

#[test]
Expand Down
15 changes: 15 additions & 0 deletions src/snapshot_middleware/json_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{borrow::Cow, collections::HashMap, path::Path, str};

use anyhow::Context;
use memofs::Vfs;
use rbx_dom_weak::types::Attributes;
use serde::Deserialize;

use crate::{
Expand Down Expand Up @@ -78,6 +79,9 @@ struct JsonModel {
skip_serializing_if = "HashMap::is_empty"
)]
properties: HashMap<String, UnresolvedValue>,

#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
attributes: HashMap<String, UnresolvedValue>,
}

impl JsonModel {
Expand All @@ -96,6 +100,17 @@ impl JsonModel {
properties.insert(key, value);
}

if !self.attributes.is_empty() {
let mut attributes = Attributes::new();

for (key, unresolved) in self.attributes {
let value = unresolved.resolve_unambiguous()?;
attributes.insert(key, value);
}

properties.insert("Attributes".into(), attributes.into());
}

Ok(InstanceSnapshot {
snapshot_id: None,
metadata: Default::default(),
Expand Down
17 changes: 17 additions & 0 deletions src/snapshot_middleware/meta_file.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{borrow::Cow, collections::HashMap, path::PathBuf};

use anyhow::{format_err, Context};
use rbx_dom_weak::types::Attributes;
use serde::{Deserialize, Serialize};

use crate::{resolution::UnresolvedValue, snapshot::InstanceSnapshot};
Expand Down Expand Up @@ -78,6 +79,9 @@ pub struct DirectoryMetadata {
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub properties: HashMap<String, UnresolvedValue>,

#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub attributes: HashMap<String, UnresolvedValue>,

#[serde(skip_serializing_if = "Option::is_none")]
pub class_name: Option<String>,

Expand Down Expand Up @@ -139,6 +143,19 @@ impl DirectoryMetadata {
snapshot.properties.insert(key, value);
}

if !self.attributes.is_empty() {
let mut attributes = Attributes::new();

for (key, unresolved) in self.attributes.drain() {
let value = unresolved.resolve_unambiguous()?;
attributes.insert(key, value);
}

snapshot
.properties
.insert("Attributes".into(), attributes.into());
}

Ok(())
}
}
18 changes: 18 additions & 0 deletions src/snapshot_middleware/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{borrow::Cow, collections::HashMap, path::Path};

use anyhow::{bail, Context};
use memofs::Vfs;
use rbx_dom_weak::types::Attributes;
use rbx_reflection::ClassTag;

use crate::{
Expand Down Expand Up @@ -231,6 +232,23 @@ pub fn snapshot_project_node(
properties.insert(key.clone(), value);
}

if !node.attributes.is_empty() {
let mut attributes = Attributes::new();

for (key, unresolved) in &node.attributes {
let value = unresolved.clone().resolve_unambiguous().with_context(|| {
format!(
"Unresolvable attribute in project at path {}",
project_path.display()
)
})?;

attributes.insert(key.clone(), value);
}

properties.insert("Attributes".into(), attributes.into());
}

// If the user specified $ignoreUnknownInstances, overwrite the existing
// value.
//
Expand Down