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/README.md

7.7 KiB

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.