163 lines
7.4 KiB
Markdown
163 lines
7.4 KiB
Markdown
# Rhai Wrapper Crate
|
|
|
|
This crate provides utilities to simplify the process of wrapping Rust functions and types for use with the Rhai scripting engine. Its primary component is the `wrap_for_rhai!` macro, which generates the necessary boilerplate to make Rust functions callable from Rhai scripts, including type conversions for arguments and return values.
|
|
|
|
This crate works in conjunction with the `rhai_macros_derive` crate, which provides `ToRhaiMap` and `FromRhaiMap` derive macros for custom Rust structs.
|
|
|
|
## Features
|
|
|
|
- **`wrap_for_rhai!` macro**: Simplifies registering Rust functions with Rhai.
|
|
- Support for various function signatures:
|
|
- Primitive types (`INT`, `FLOAT`, `bool`, `String`).
|
|
- `Option<T>` for optional return values.
|
|
- `Vec<PrimitiveType>` (e.g., `Vec<INT>`, `Vec<String>`).
|
|
- `Vec<CustomStruct>` where `CustomStruct` implements `FromRhaiMap` (for arguments) and `ToRhaiMap` (for elements in return values).
|
|
- Custom structs as direct arguments (if they implement `FromRhaiMap`).
|
|
- Custom structs as direct return values (if they implement `ToRhaiMap`).
|
|
- Automatic conversion between Rhai's `Dynamic` type and Rust types.
|
|
|
|
## Dependencies
|
|
|
|
Ensure you have `rhai` and `rhai_macros_derive` (if using custom structs) in your `Cargo.toml`:
|
|
|
|
```toml
|
|
[dependencies]
|
|
rhai = "<version>" # e.g., "1.16.0"
|
|
rhai_macros_derive = { path = "../rhai_macros_derive" } # If in the same workspace
|
|
|
|
# This crate (rhai_wrapper) would also be a local path dependency
|
|
# rhai_wrapper = { path = "../rhai_wrapper" }
|
|
```
|
|
|
|
## `wrap_for_rhai!` Macro
|
|
|
|
The `wrap_for_rhai!` macro is the core of this crate. It takes your Rust function and its type signature (in a specific format) and generates a closure that Rhai can register.
|
|
|
|
### Basic Usage
|
|
|
|
```rust
|
|
use rhai_wrapper::wrap_for_rhai;
|
|
use rhai::{Engine, INT, FLOAT};
|
|
|
|
// Functions to be wrapped
|
|
fn add(a: INT, b: INT) -> INT { a + b }
|
|
fn greet(name: String) -> String { format!("Hello, {}!", name) }
|
|
fn get_pi() -> FLOAT { 3.14159 }
|
|
|
|
fn main() {
|
|
let mut engine = Engine::new();
|
|
|
|
// Registering functions using the macro
|
|
engine.register_fn("add_rhai", wrap_for_rhai!(add, INT, INT -> INT));
|
|
engine.register_fn("greet_rhai", wrap_for_rhai!(greet, String -> String));
|
|
engine.register_fn("get_pi_rhai", wrap_for_rhai!(get_pi, -> FLOAT));
|
|
|
|
let result: INT = engine.eval("add_rhai(10, 32)").unwrap();
|
|
assert_eq!(result, 42);
|
|
|
|
let message: String = engine.eval(r#"greet_rhai("Rhai")"#).unwrap();
|
|
assert_eq!(message, "Hello, Rhai!");
|
|
}
|
|
```
|
|
|
|
### Supported Signatures & Examples
|
|
|
|
The macro uses a pattern-matching style to handle different function signatures.
|
|
|
|
1. **Primitives**: `(INT, INT -> INT)`, `(String -> String)`, `(FLOAT, bool -> String)`
|
|
```rust
|
|
fn my_func(a: INT, b: String) -> bool { /* ... */ false }
|
|
// engine.register_fn("my_func_rhai", wrap_for_rhai!(my_func, INT, String -> bool));
|
|
```
|
|
|
|
2. **No Arguments**: `(-> INT)`
|
|
```rust
|
|
fn get_answer() -> INT { 42 }
|
|
// engine.register_fn("get_answer_rhai", wrap_for_rhai!(get_answer, -> INT));
|
|
```
|
|
|
|
3. **`Option<T>` Return Type**: `(INT -> Option<String>)`
|
|
```rust
|
|
fn maybe_get_name(id: INT) -> Option<String> {
|
|
if id == 1 { Some("Alice".to_string()) } else { None }
|
|
}
|
|
// engine.register_fn("maybe_get_name_rhai", wrap_for_rhai!(maybe_get_name, INT -> Option<String>));
|
|
```
|
|
*Rhai will receive `()` (unit/nothing) if the Rust function returns `None`.*
|
|
|
|
4. **`Vec<Primitive>` Argument**: `(Vec<INT> -> INT)`, `(Vec<String> -> String)`
|
|
```rust
|
|
fn sum_numbers(numbers: Vec<INT>) -> INT { numbers.iter().sum() }
|
|
// engine.register_fn("sum_rhai", wrap_for_rhai!(sum_numbers, Vec<INT> -> INT));
|
|
// Rhai script: sum_rhai([1, 2, 3]) -> 6
|
|
```
|
|
|
|
5. **Custom Structs (with `rhai_macros_derive`)**
|
|
|
|
Assume you have a struct `Point` that derives `ToRhaiMap` and `FromRhaiMap`:
|
|
```rust
|
|
use rhai_macros_derive::{ToRhaiMap, FromRhaiMap};
|
|
use rhai::CustomType;
|
|
|
|
#[derive(CustomType, ToRhaiMap, FromRhaiMap, Clone, Debug, PartialEq)]
|
|
struct Point { x: INT, y: INT }
|
|
```
|
|
|
|
- **Custom Struct Argument**: `(Point -> String)`
|
|
```rust
|
|
fn print_point(p: Point) -> String { format!("Point(x={}, y={})", p.x, p.y) }
|
|
// engine.build_type::<Point>();
|
|
// engine.register_fn("print_point_rhai", wrap_for_rhai!(print_point, Point -> String));
|
|
// Rhai script: print_point_rhai(#{x:1, y:2})
|
|
```
|
|
|
|
- **Custom Struct Return**: `(INT, INT -> Point)`
|
|
```rust
|
|
fn make_point(x: INT, y: INT) -> Point { Point { x, y } }
|
|
// engine.build_type::<Point>();
|
|
// engine.register_fn("make_point_rhai", wrap_for_rhai!(make_point, INT, INT -> Point));
|
|
// Rhai script: let p = make_point_rhai(3,4); p.x == 3
|
|
```
|
|
|
|
- **`Vec<CustomStruct>` Argument**: `(Vec<Point> -> INT)`
|
|
```rust
|
|
fn sum_point_coords(points: Vec<Point>) -> INT { points.iter().map(|p| p.x + p.y).sum() }
|
|
// engine.build_type::<Point>();
|
|
// engine.register_fn("sum_points_rhai", wrap_for_rhai!(sum_point_coords, Vec<Point> -> INT));
|
|
// Rhai script: sum_points_rhai([#{x:1,y:2}, #{x:3,y:4}])
|
|
```
|
|
|
|
- **`Vec<CustomStruct>` Return**: `(INT -> Vec<Point>)`
|
|
```rust
|
|
fn generate_points(count: INT) -> Vec<Point> {
|
|
(0..count).map(|i| Point { x: i, y: i*2 }).collect()
|
|
}
|
|
// engine.build_type::<Point>();
|
|
// engine.register_fn("gen_points_rhai", wrap_for_rhai!(generate_points, INT -> Vec<Point>));
|
|
// Rhai script: let arr = gen_points_rhai(2); arr[0].x == 0
|
|
```
|
|
|
|
- **`Vec<CustomStruct>` Argument and `Vec<Primitive>` Return**: `(Vec<Point> -> Vec<INT>)`
|
|
```rust
|
|
fn get_x_coords(points: Vec<Point>) -> Vec<INT> { points.iter().map(|p| p.x).collect() }
|
|
// engine.build_type::<Point>();
|
|
// engine.register_fn("get_xs_rhai", wrap_for_rhai!(get_x_coords, Vec<Point> -> Vec<INT>));
|
|
```
|
|
|
|
### How Custom Structs are Handled
|
|
|
|
- When a custom struct is an **argument** (`MyStructType`): The macro expects the Rhai script to pass an object map. This map is then passed to `MyStructType::from_rhai_map(map)` (provided by `#[derive(FromRhaiMap)]`) to convert it into a Rust struct instance.
|
|
- When a custom struct is a **return value**: The Rust function returns an instance of `MyStructType`. The macro calls `instance.to_rhai_map()` (provided by `#[derive(ToRhaiMap)]`) to convert it into a `rhai::Map`, which Rhai receives as an object map.
|
|
- For `Vec<CustomStruct>`: Similar logic applies element-wise. Incoming `rhai::Array` elements are converted using `from_rhai_map`; outgoing `Vec` elements are converted using `to_rhai_map` before being collected into a `rhai::Array`.
|
|
|
|
## Adding New Macro Arms
|
|
|
|
The `wrap_for_rhai!` macro is defined with several arms, each matching a specific function signature pattern. If you need to support a new, common signature:
|
|
1. Open `rhai_wrapper/src/lib.rs`.
|
|
2. Add a new macro arm following the existing patterns.
|
|
3. Pay attention to argument conversion (from `rhai::Dynamic` or `rhai::Map`) and return value conversion (to `rhai::Dynamic` or `rhai::Map`).
|
|
4. For custom types, rely on `YourType::from_rhai_map(...)` and `your_instance.to_rhai_map()`.
|
|
5. Consider the order of macro arms if a more specific arm needs to match before a more general one.
|
|
|
|
This crate aims to reduce boilerplate and make Rhai integration smoother for common Rust patterns.
|