This repository has been archived on 2025-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
rhaj/rhai_macros_derive
2025-05-12 02:31:45 +03:00
..
src create macros for generating rhai wrappers and add tests 2025-05-12 02:31:45 +03:00
Cargo.lock create macros for generating rhai wrappers and add tests 2025-05-12 02:31:45 +03:00
Cargo.toml create macros for generating rhai wrappers and add tests 2025-05-12 02:31:45 +03:00
README.md create macros for generating rhai wrappers and add tests 2025-05-12 02:31:45 +03:00

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:

[dependencies]
rhai_macros_derive = { path = "../rhai_macros_derive" }
# ... other dependencies

And rhai itself:

[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

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::Maps 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

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:

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.