Skip to content
This repository has been archived by the owner on Jul 22, 2023. It is now read-only.

Commit

Permalink
Implement remaining raw property access API
Browse files Browse the repository at this point in the history
  • Loading branch information
LPGhatguy committed Sep 28, 2019
1 parent 2b2c574 commit 00843a5
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 10 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* **Breaking:** `Instance.new` now only works for instances that actually exist.
* Added `Instance:Clone()` for copying instances all over the place, as is Roblox tradition. ([#12](https://github.com/rojo-rbx/remodel/issues/12))
* Added `DataModel:GetService()` for finding services and creating them if they don't exist, like Roblox does. ([#10](https://github.com/rojo-rbx/remodel/issues/10))
* Added `remodel.getRawProperty(instance, name)` for an initial stab at reading property values.
* Added `remodel.getRawProperty(instance, name)`, a clunky but powerful API for reading properties with no validation.
* Added `remodel.setRawProperty(instance, name, type, value)` for writing properties with no validation.
* Fixed Remodel dropping unknown properties when reading/writing XML models. This should make Remodel's behavior line up with Rojo.
* Improved error messages in preparation for [#7](https://github.com/rojo-rbx/remodel/issues/7) to be fixed upstream.

Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,33 @@ If the instance is a `DataModel`, this method will throw. Places should be uploa

Throws on error.

### `remodel.getRawProperty` (**unreleased**)
```
remodel.getRawProperty(instance: Instance, name: string): any?
```

Gets the property with the given name from the given instance, bypassing all validation.

This is intended to be a simple to implement but very powerful API while Remodel grows more ergonomic functionality.

Throws if the value type stored on the instance cannot be represented by Remodel yet. See [Supported Roblox Types](#supported-roblox-types) for more details.

### `remodel.setRawProperty` (**unreleased**)
```
remodel.setRawProperty(
instance: Instance,
name: string,
type: string,
value: any,
)
```

Sets a property on the given instance with the name, type, and value given. Valid values for `type` are defined in [Supported Roblox Types](#supported-roblox-types) in the left half of the bulleted list.

This is intended to be a simple to implement but very powerful API while Remodel grows more ergonomic functionality.

Throws if the value type cannot be represented by Remodel yet. See [Supported Roblox Types](#supported-roblox-types) for more details.

### `remodel.readFile` (0.3.0+)
```
remodel.readFile(path: string): string
Expand Down Expand Up @@ -202,6 +229,21 @@ This is a thin wrapper around Rust's [`fs::create_dir_all`](https://doc.rust-lan

Throws on error.

## Supported Roblox Types
When interacting with Roblox instances, Remodel doesn't support all value types yet and may throw an error.

Supported types and their Lua equivalents:

* `String`: `string`
* `Content`: `string`
* `Bool`: `boolean`
* `Float64`: `number`
* `Float32`: `number`
* `Int64`: `number`
* `Int32`: `number`

More types will be added as time goes on, and Remodel will slowly begin to automatically infer correct types in more contexts.

## Authentication
Some of Remodel's APIs access the Roblox web API and need authentication in the form of a `.ROBLOSECURITY` cookie to access private assets. Auth cookies look like this:

Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod auth_cookie;
mod remodel_api;
mod remodel_context;
mod roblox_api;
mod value;

use std::{
error::Error,
Expand Down
44 changes: 36 additions & 8 deletions src/remodel_api/remodel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ use std::{
sync::Arc,
};

use rbx_dom_weak::{RbxInstanceProperties, RbxTree, RbxValue};
use rbx_dom_weak::{RbxInstanceProperties, RbxTree, RbxValueType};
use reqwest::header::{CONTENT_TYPE, COOKIE, USER_AGENT};
use rlua::{Context, ToLua, UserData, UserDataMethods};
use rlua::{Context, UserData, UserDataMethods};

use super::LuaInstance;
use crate::remodel_context::RemodelContext;
use crate::{
remodel_context::RemodelContext,
value::{lua_to_rbxvalue, rbxvalue_to_lua, type_from_str},
};

fn xml_encode_options() -> rbx_xml::EncodeOptions {
rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown)
Expand Down Expand Up @@ -306,17 +309,32 @@ impl Remodel {
let tree = lua_instance.tree.lock().unwrap();

let instance = tree.get_instance(lua_instance.id).ok_or_else(|| {
rlua::Error::external("Cannot call remodel.GetRawProperty on a destroyed instance.")
rlua::Error::external("Cannot call remodel.getRawProperty on a destroyed instance.")
})?;

match instance.properties.get(name) {
Some(value) => match value {
RbxValue::String { value } => value.as_str().to_lua(context),
_ => unimplemented!(),
},
Some(value) => rbxvalue_to_lua(context, value),
None => Ok(rlua::Value::Nil),
}
}

fn set_raw_property(
lua_instance: LuaInstance,
key: String,
ty: RbxValueType,
lua_value: rlua::Value<'_>,
) -> rlua::Result<()> {
let mut tree = lua_instance.tree.lock().unwrap();

let instance = tree.get_instance_mut(lua_instance.id).ok_or_else(|| {
rlua::Error::external("Cannot call remodel.setRawProperty on a destroyed instance.")
})?;

let value = lua_to_rbxvalue(ty, lua_value)?;
instance.properties.insert(key, value);

Ok(())
}
}

impl UserData for Remodel {
Expand All @@ -328,6 +346,16 @@ impl UserData for Remodel {
},
);

methods.add_function(
"setRawProperty",
|_context, (instance, name, lua_ty, value): (LuaInstance, String, String, rlua::Value<'_>)| {
let ty = type_from_str(&lua_ty)
.ok_or_else(|| rlua::Error::external(format!("{} is not a valid Roblox type.", lua_ty)))?;

Self::set_raw_property(instance, name, ty, value)
},
);

methods.add_function("readPlaceFile", |context, lua_path: String| {
let path = Path::new(&lua_path);

Expand Down
134 changes: 134 additions & 0 deletions src/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//! Defines how to turn RbxValue values into Lua values and back.
use rbx_dom_weak::{RbxValue, RbxValueType};
use rlua::{Context, Result as LuaResult, ToLua, Value as LuaValue};

pub fn rbxvalue_to_lua<'lua>(
context: Context<'lua>,
value: &RbxValue,
) -> LuaResult<LuaValue<'lua>> {
use RbxValue::*;

fn unimplemented_type(name: &str) -> LuaResult<LuaValue<'_>> {
Err(rlua::Error::external(format!(
"Values of type {} are not yet implemented.",
name
)))
}

match value {
BinaryString { value: _ } => unimplemented_type("BinaryString"),
BrickColor { value: _ } => unimplemented_type("BrickColor"),
Bool { value } => value.to_lua(context),
CFrame { value: _ } => unimplemented_type("CFrame"),
Color3 { value: _ } => unimplemented_type("Color3"),
Color3uint8 { value: _ } => unimplemented_type("Color3uint8"),
ColorSequence { value: _ } => unimplemented_type("ColorSequence"),
Content { value } => value.as_str().to_lua(context),
Enum { value: _ } => unimplemented_type("Enum"),
Float32 { value } => value.to_lua(context),
Float64 { value } => value.to_lua(context),
Int32 { value } => value.to_lua(context),
Int64 { value } => value.to_lua(context),
NumberRange { value: _ } => unimplemented_type("NumberRange"),
NumberSequence { value: _ } => unimplemented_type("NumberSequence"),
PhysicalProperties { value: _ } => unimplemented_type("PhysicalProperties"),
Ray { value: _ } => unimplemented_type("Ray"),
Rect { value: _ } => unimplemented_type("Rect"),
Ref { value: _ } => unimplemented_type("Ref"),
SharedString { value: _ } => unimplemented_type("SharedString"),
String { value } => value.as_str().to_lua(context),
UDim { value: _ } => unimplemented_type("UDim"),
UDim2 { value: _ } => unimplemented_type("UDim2"),
Vector2 { value: _ } => unimplemented_type("Vector2"),
Vector2int16 { value: _ } => unimplemented_type("Vector2int16"),
Vector3 { value: _ } => unimplemented_type("Vector3"),
Vector3int16 { value: _ } => unimplemented_type("Vector3int16"),

_ => Err(rlua::Error::external(format!(
"The type '{:?}' is unknown to Remodel, please file a bug!",
value.get_type()
))),
}
}

pub fn lua_to_rbxvalue(ty: RbxValueType, value: LuaValue<'_>) -> LuaResult<RbxValue> {
match (ty, value) {
(RbxValueType::String, LuaValue::String(lua_string)) => Ok(RbxValue::String {
value: lua_string.to_str()?.to_owned(),
}),
(RbxValueType::Content, LuaValue::String(lua_string)) => Ok(RbxValue::String {
value: lua_string.to_str()?.to_owned(),
}),

(RbxValueType::Bool, LuaValue::Boolean(value)) => Ok(RbxValue::Bool { value }),

(RbxValueType::Float32, LuaValue::Number(value)) => Ok(RbxValue::Float32 {
value: value as f32,
}),
(RbxValueType::Float32, LuaValue::Integer(value)) => Ok(RbxValue::Float32 {
value: value as f32,
}),

(RbxValueType::Float64, LuaValue::Number(value)) => Ok(RbxValue::Float64 {
value: value as f64,
}),
(RbxValueType::Float64, LuaValue::Integer(value)) => Ok(RbxValue::Float64 {
value: value as f64,
}),

(RbxValueType::Int32, LuaValue::Number(value)) => Ok(RbxValue::Int32 {
value: value as i32,
}),
(RbxValueType::Int32, LuaValue::Integer(value)) => Ok(RbxValue::Int32 {
value: value as i32,
}),

(RbxValueType::Int64, LuaValue::Number(value)) => Ok(RbxValue::Int64 {
value: value as i64,
}),
(RbxValueType::Int64, LuaValue::Integer(value)) => Ok(RbxValue::Int64 {
value: value as i64,
}),

(_, unknown_value) => Err(rlua::Error::external(format!(
"The Lua value {:?} could not be converted to the Roblox type {:?}",
unknown_value, ty
))),
}
}

pub fn type_from_str(name: &str) -> Option<RbxValueType> {
use RbxValueType::*;

match name {
"BinaryString" => Some(BinaryString),
"BrickColor" => Some(BrickColor),
"Bool" => Some(Bool),
"CFrame" => Some(CFrame),
"Color3" => Some(Color3),
"Color3uint8" => Some(Color3uint8),
"ColorSequence" => Some(ColorSequence),
"Content" => Some(Content),
"Enum" => Some(Enum),
"Float32" => Some(Float32),
"Float64" => Some(Float64),
"Int32" => Some(Int32),
"Int64" => Some(Int64),
"NumberRange" => Some(NumberRange),
"NumberSequence" => Some(NumberSequence),
"PhysicalProperties" => Some(PhysicalProperties),
"Ray" => Some(Ray),
"Rect" => Some(Rect),
"Ref" => Some(Ref),
"SharedString" => Some(SharedString),
"String" => Some(String),
"UDim" => Some(UDim),
"UDim2" => Some(UDim2),
"Vector2" => Some(Vector2),
"Vector2int16" => Some(Vector2int16),
"Vector3" => Some(Vector3),
"Vector3int16" => Some(Vector3int16),
_ => None,
}
}
7 changes: 7 additions & 0 deletions test-scripts/raw-property-bad-conversion.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
local folder = Instance.new("Folder")

local ok = pcall(function()
remodel.setRawProperty(folder, "PROPERTY_NAME", "Float64", "hello")
end)

assert(not ok)
5 changes: 5 additions & 0 deletions test-scripts/raw-property-new.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
local value = Instance.new("NumberValue")
remodel.setRawProperty(value, "Value", "Float64", 32)

local value = remodel.getRawProperty(value, "Value")
assert(value == 32)
11 changes: 11 additions & 0 deletions test-scripts/raw-property-number.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
local model = remodel.readModelFile("test-models/folder-and-value.rbxmx")[1]

local numberValue = model.Number
assert(numberValue.ClassName == "NumberValue")

local value = remodel.getRawProperty(numberValue, "Value")
assert(value == 42)

remodel.setRawProperty(numberValue, "Value", "Float64", 8)
local value = remodel.getRawProperty(numberValue, "Value")
assert(value == 8)
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ local stringValue = model.String
assert(stringValue.ClassName == "StringValue")

local value = remodel.getRawProperty(stringValue, "Value")
assert(value == "Hello")
assert(value == "Hello")

remodel.setRawProperty(stringValue, "Value", "String", "Hello, world!")
local value = remodel.getRawProperty(stringValue, "Value")
assert(value == "Hello, world!")

0 comments on commit 00843a5

Please sign in to comment.