201 lines
7.7 KiB
Markdown
201 lines
7.7 KiB
Markdown
# Rhai Macros Derive Crate
|
|
|
|
This crate provides procedural macros to simplify the integration of custom Rust structs with the Rhai scripting engine, specifically for converting structs to and from `rhai::Map` objects. It is intended to be used alongside the `rhai_wrapper` crate.
|
|
|
|
## Provided Macros
|
|
|
|
- `#[derive(ToRhaiMap)]`
|
|
- `#[derive(FromRhaiMap)]`
|
|
|
|
## Dependencies
|
|
|
|
Make sure this crate is included in your `Cargo.toml` dependencies, typically as a local path dependency if working within the `rhaj` project:
|
|
|
|
```toml
|
|
[dependencies]
|
|
rhai_macros_derive = { path = "../rhai_macros_derive" }
|
|
# ... other dependencies
|
|
```
|
|
|
|
And `rhai` itself:
|
|
```toml
|
|
[dependencies]
|
|
rhai = "<version>" # e.g., "1.16.0"
|
|
```
|
|
|
|
## `#[derive(ToRhaiMap)]`
|
|
|
|
This macro automatically generates an implementation of a `to_rhai_map(&self) -> rhai::Map` method for your struct. This method converts an instance of your struct into a `rhai::Map`, which can then be easily used within Rhai scripts as an object map.
|
|
|
|
### Usage
|
|
|
|
```rust
|
|
use rhai_macros_derive::ToRhaiMap;
|
|
use rhai::{INT, FLOAT, Map};
|
|
|
|
// Forward declaration for Point if used in Vec<Point>
|
|
// Typically Point would also derive ToRhaiMap and FromRhaiMap
|
|
#[derive(Debug, Clone, PartialEq, ToRhaiMap)] // Assuming Point also derives ToRhaiMap
|
|
struct Point {
|
|
x: INT,
|
|
y: INT,
|
|
}
|
|
|
|
impl Point { // Minimal stub for example if not fully defined elsewhere
|
|
fn to_rhai_map(&self) -> Map {
|
|
let mut map = Map::new();
|
|
map.insert("x".into(), self.x.into());
|
|
map.insert("y".into(), self.y.into());
|
|
map
|
|
}
|
|
}
|
|
|
|
#[derive(ToRhaiMap)]
|
|
struct MyStruct {
|
|
id: INT,
|
|
name: String,
|
|
is_active: bool,
|
|
score: FLOAT,
|
|
position: Point, // Nested struct
|
|
tags: Vec<String>, // Vec of primitives
|
|
history: Vec<Point>, // Vec of custom structs
|
|
}
|
|
|
|
fn main() {
|
|
let p = Point { x: 10, y: 20 };
|
|
let my_instance = MyStruct {
|
|
id: 1,
|
|
name: "Test".to_string(),
|
|
is_active: true,
|
|
score: 99.5,
|
|
position: p.clone(),
|
|
tags: vec!["alpha".to_string(), "beta".to_string()],
|
|
history: vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4}],
|
|
};
|
|
|
|
let rhai_map = my_instance.to_rhai_map();
|
|
|
|
assert_eq!(rhai_map.get("id").unwrap().as_int().unwrap(), 1);
|
|
assert_eq!(rhai_map.get("name").unwrap().clone().into_string().unwrap(), "Test");
|
|
assert_eq!(rhai_map.get("is_active").unwrap().as_bool().unwrap(), true);
|
|
assert_eq!(rhai_map.get("score").unwrap().as_float().unwrap(), 99.5);
|
|
|
|
let pos_map = rhai_map.get("position").unwrap().clone().try_cast::<Map>().unwrap();
|
|
assert_eq!(pos_map.get("x").unwrap().as_int().unwrap(), 10);
|
|
|
|
let tags_array = rhai_map.get("tags").unwrap().clone().try_cast::<rhai::Array>().unwrap();
|
|
assert_eq!(tags_array.len(), 2);
|
|
assert_eq!(tags_array[0].clone().into_string().unwrap(), "alpha");
|
|
|
|
let history_array = rhai_map.get("history").unwrap().clone().try_cast::<rhai::Array>().unwrap();
|
|
assert_eq!(history_array.len(), 2);
|
|
let hist_p1_map = history_array[0].clone().try_cast::<Map>().unwrap();
|
|
assert_eq!(hist_p1_map.get("x").unwrap().as_int().unwrap(), 1);
|
|
}
|
|
```
|
|
|
|
### How it works:
|
|
|
|
- **Primitive Types**: Fields like `INT`, `i64`, `String`, `FLOAT`, `f64`, and `bool` are cloned and converted into their `rhai::Dynamic` equivalents.
|
|
- **Nested Structs**: If a field is another struct (e.g., `position: Point`), that struct must also implement `to_rhai_map()`. The macro will call `self.field_name.to_rhai_map()` for that field.
|
|
- **`Vec<T>` Fields**:
|
|
- If `T` is a primitive type (e.g., `Vec<String>`), each element is cloned and converted to `rhai::Dynamic`, then collected into a `rhai::Array`.
|
|
- If `T` is a custom struct (e.g., `Vec<Point>`), `item.to_rhai_map()` is called for each element, and the resulting `rhai::Map`s are collected into a `rhai::Array`.
|
|
|
|
## `#[derive(FromRhaiMap)]`
|
|
|
|
This macro automatically generates an implementation of `from_rhai_map(map: rhai::Map) -> Result<Self, String>` for your struct. This method attempts to construct an instance of your struct from a `rhai::Map`.
|
|
|
|
### Usage
|
|
|
|
```rust
|
|
use rhai_macros_derive::FromRhaiMap;
|
|
use rhai::{INT, FLOAT, Map, Array, Dynamic};
|
|
|
|
// Assuming Point also derives FromRhaiMap and has a from_rhai_map method
|
|
#[derive(Debug, Clone, PartialEq, FromRhaiMap)]
|
|
struct Point {
|
|
x: INT,
|
|
y: INT,
|
|
}
|
|
|
|
impl Point { // Minimal stub for example
|
|
fn from_rhai_map(mut map: Map) -> Result<Self, String> {
|
|
Ok(Point {
|
|
x: map.get("x").and_then(|d| d.as_int().ok()).ok_or("x missing")?,
|
|
y: map.get("y").and_then(|d| d.as_int().ok()).ok_or("y missing")?,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(FromRhaiMap, Debug, PartialEq)] // Added Debug, PartialEq for assert
|
|
struct MyStruct {
|
|
id: INT,
|
|
name: String,
|
|
is_active: bool,
|
|
score: FLOAT,
|
|
position: Point, // Nested struct
|
|
tags: Vec<String>, // Vec of primitives
|
|
history: Vec<Point>, // Vec of custom structs
|
|
}
|
|
|
|
fn main() {
|
|
let mut map = Map::new();
|
|
map.insert("id".into(), (1 as INT).into());
|
|
map.insert("name".into(), "Test".to_string().into());
|
|
map.insert("is_active".into(), true.into());
|
|
map.insert("score".into(), (99.5 as FLOAT).into());
|
|
|
|
let mut pos_map = Map::new();
|
|
pos_map.insert("x".into(), (10 as INT).into());
|
|
pos_map.insert("y".into(), (20 as INT).into());
|
|
map.insert("position".into(), pos_map.into());
|
|
|
|
let tags_array: Array = vec![Dynamic::from("alpha".to_string()), Dynamic::from("beta".to_string())];
|
|
map.insert("tags".into(), tags_array.into());
|
|
|
|
let mut hist_p1_map = Map::new();
|
|
hist_p1_map.insert("x".into(), (1 as INT).into());
|
|
hist_p1_map.insert("y".into(), (2 as INT).into());
|
|
let mut hist_p2_map = Map::new();
|
|
hist_p2_map.insert("x".into(), (3 as INT).into());
|
|
hist_p2_map.insert("y".into(), (4 as INT).into());
|
|
let history_array: Array = vec![Dynamic::from(hist_p1_map), Dynamic::from(hist_p2_map)];
|
|
map.insert("history".into(), history_array.into());
|
|
|
|
let my_instance = MyStruct::from_rhai_map(map).unwrap();
|
|
|
|
assert_eq!(my_instance.id, 1);
|
|
assert_eq!(my_instance.name, "Test");
|
|
assert_eq!(my_instance.position, Point { x: 10, y: 20 });
|
|
assert_eq!(my_instance.tags, vec!["alpha".to_string(), "beta".to_string()]);
|
|
assert_eq!(my_instance.history, vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4}]);
|
|
}
|
|
```
|
|
|
|
### How it works:
|
|
|
|
- **Primitive Types**: For fields like `INT`, `String`, etc., the macro attempts to retrieve the value from the input `rhai::Map` by its key (field name) and convert it to the expected Rust type (e.g., using `as_int()`, `into_string()`).
|
|
- **Nested Structs**: If a field is another struct, it retrieves the corresponding value as a `rhai::Dynamic`, tries to cast it to `rhai::Map`, and then calls `NestedStructType::from_rhai_map()` on that sub-map.
|
|
- **`Vec<T>` Fields**:
|
|
- Retrieves the value as `rhai::Dynamic`, casts it to `rhai::Array`.
|
|
- If `T` is a primitive type, it iterates the array, converting each `rhai::Dynamic` element to `T`.
|
|
- If `T` is a custom struct, it iterates the array, casting each `rhai::Dynamic` element to `rhai::Map`, and then calls `T::from_rhai_map()` on each sub-map.
|
|
- **Error Handling**: If a field is missing, or if a type conversion fails, `from_rhai_map` will return an `Err(String)` describing the issue.
|
|
|
|
## Combining with `rhai::CustomType`
|
|
|
|
For your structs to be fully usable as custom types within Rhai (e.g., to be registered with `engine.build_type::<MyStruct>()`), they should also typically derive `rhai::CustomType`, `Clone`, and `Debug`:
|
|
|
|
```rust
|
|
use rhai_macros_derive::{ToRhaiMap, FromRhaiMap};
|
|
use rhai::CustomType;
|
|
|
|
#[derive(CustomType, ToRhaiMap, FromRhaiMap, Clone, Debug, PartialEq)]
|
|
struct MyStruct {
|
|
// ... fields
|
|
}
|
|
```
|
|
|
|
This setup allows seamless conversion and manipulation of your Rust structs within Rhai scripts.
|