create macros for generating rhai wrappers and add tests
This commit is contained in:
297
rhai_wrapper/Cargo.lock
generated
Normal file
297
rhai_wrapper/Cargo.lock
generated
Normal file
@@ -0,0 +1,297 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"const-random",
|
||||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags",
|
||||
"instant",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rhai_codegen",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"thin-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_codegen"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_macros_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_wrapper"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rhai",
|
||||
"rhai_macros_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-vec"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
12
rhai_wrapper/Cargo.toml
Normal file
12
rhai_wrapper/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "rhai_wrapper"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
description = "A wrapper to make generic Rust functions Rhai-compatible."
|
||||
|
||||
[dependencies]
|
||||
rhai = "1.21.0"
|
||||
rhai_macros_derive = { path = "../rhai_macros_derive" }
|
||||
|
||||
[dev-dependencies]
|
162
rhai_wrapper/README.md
Normal file
162
rhai_wrapper/README.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# 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.
|
210
rhai_wrapper/src/lib.rs
Normal file
210
rhai_wrapper/src/lib.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
//! # Rhai Wrapper
|
||||
//! Provides a macro to simplify wrapping Rust functions for the Rhai scripting engine.
|
||||
//!
|
||||
//! This crate provides a macro and utilities to wrap generic Rust functions so they can be registered with the Rhai scripting engine.
|
||||
//! It currently supports functions with primitive arguments (i64, f64, String) and return values.
|
||||
|
||||
use rhai::Map; // Kept Map for traits. Dynamic, INT, FLOAT removed as they are qualified in macro.
|
||||
|
||||
/// Trait for converting a Rust struct into a Rhai `Map`.
|
||||
/// This trait is intended to be derived using `#[derive(ToRhaiMap)]` from the `rhai_macros_derive` crate.
|
||||
pub trait ToRhaiMap {
|
||||
/// Converts `&self` into a `rhai::Map`.
|
||||
fn to_rhai_map(&self) -> Map;
|
||||
}
|
||||
|
||||
/// Trait for converting a Rhai `Map` into a Rust struct.
|
||||
/// This trait is intended to be derived using `#[derive(FromRhaiMap)]` from the `rhai_macros_derive` crate.
|
||||
pub trait FromRhaiMap: Sized {
|
||||
/// Attempts to convert a `rhai::Map` into an instance of `Self`.
|
||||
/// Returns a `Result` which is `Ok(Self)` on success, or an `Err(String)` describing the error.
|
||||
fn from_rhai_map(map: Map) -> Result<Self, String>;
|
||||
}
|
||||
|
||||
/// Macro to wrap a Rust function for Rhai
|
||||
///
|
||||
/// Usage:
|
||||
/// ```
|
||||
/// use rhai_wrapper::wrap_for_rhai;
|
||||
/// use rhai::{Engine, INT};
|
||||
/// fn add(a: INT, b: INT) -> INT { a + b }
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.register_fn("add", wrap_for_rhai!(add));
|
||||
/// let result = engine.eval::<INT>("add(2, 3)").unwrap();
|
||||
/// assert_eq!(result, 5);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! wrap_for_rhai {
|
||||
// New arm for functions like: fn name(Vec<rhai::INT>) -> rhai::INT
|
||||
// Usage: wrap_for_rhai!(my_function_name, Vec<INT> -> INT)
|
||||
($func:ident, Vec<INT> -> INT) => {
|
||||
|arr: rhai::Array| -> rhai::INT {
|
||||
let vec_arg: std::vec::Vec<INT> = arr.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.as_int().expect("Rhai array element is not an INT or could not be converted"))
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// New arm for functions like: fn name(Vec<String>) -> String
|
||||
// Usage: wrap_for_rhai!(my_function_name, Vec<String> -> String)
|
||||
($func:ident, Vec<String> -> String) => {
|
||||
|arr: rhai::Array| -> String {
|
||||
let vec_arg: std::vec::Vec<String> = arr.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.into_string().expect("Rhai array element is not a String or could not be converted"))
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// New arm for functions like: fn name(Vec<FLOAT>) -> FLOAT
|
||||
// Usage: wrap_for_rhai!(my_function_name, Vec<FLOAT> -> FLOAT)
|
||||
($func:ident, Vec<FLOAT> -> FLOAT) => {
|
||||
|arr: rhai::Array| -> rhai::FLOAT {
|
||||
let vec_arg: std::vec::Vec<FLOAT> = arr.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.as_float().expect("Rhai array element is not a FLOAT or could not be converted"))
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// Specific arms for Vec<CustomStruct> -> Vec<PrimitiveType>
|
||||
// These must come BEFORE the Vec<CustomIn> -> Vec<CustomOut> arm.
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<INT>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<rhai::INT> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<FLOAT>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<rhai::FLOAT> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<String>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<String> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<bool>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<bool> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
|
||||
// Arm for Vec<CustomStructIn> -> Vec<CustomStructOut>
|
||||
// This must come AFTER specific primitive Vec outputs to allow them to match first.
|
||||
// Requires $InputStructType::from_rhai_map and $OutputStructType::to_rhai_map
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<$OutputStructType:ident>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>()
|
||||
.expect("Rhai array element is not an object map for $InputStructType conversion");
|
||||
$InputStructType::from_rhai_map(map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let result_vec: Vec<$OutputStructType> = $func(vec_arg);
|
||||
|
||||
result_vec.into_iter()
|
||||
.map(|item: $OutputStructType| item.to_rhai_map().into()) // Convert struct to rhai::Map, then to rhai::Dynamic
|
||||
.collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
|
||||
// Func(CustomStruct) -> String
|
||||
($func:ident, $InputStructType:ident -> String) => {
|
||||
|arg_dyn: rhai::Dynamic| -> String {
|
||||
let arg_map = arg_dyn.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Argument not an object map for {}", stringify!($InputStructType)));
|
||||
let arg_struct = $InputStructType::from_rhai_map(arg_map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($InputStructType)));
|
||||
$func(arg_struct)
|
||||
}
|
||||
};
|
||||
|
||||
// Func(String, INT, INT) -> CustomReturnType
|
||||
($func:ident, String, INT, INT -> $ReturnType:ident) => {
|
||||
|id_str: rhai::ImmutableString, cx_int: rhai::INT, cy_int: rhai::INT| -> rhai::Dynamic {
|
||||
let id = id_str.to_string();
|
||||
// cx_int and cy_int are already rhai::INT, matching typical Rust INT type alias for i64
|
||||
|
||||
let result_struct: $ReturnType = $func(id, cx_int, cy_int);
|
||||
let result_map = result_struct.to_rhai_map();
|
||||
rhai::Dynamic::from(result_map)
|
||||
}
|
||||
};
|
||||
|
||||
// Func(CustomType1, CustomType2, String) -> CustomTypeReturn
|
||||
($func:ident, $Arg1Type:ident, $Arg2Type:ident, $Arg3Type:ident -> $ReturnType:ident) => {
|
||||
|arg1_dyn: rhai::Dynamic, arg2_dyn: rhai::Dynamic, arg3_str: rhai::ImmutableString| -> rhai::Dynamic {
|
||||
let arg1_map = arg1_dyn.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Argument 1 not an object map for {}", stringify!($Arg1Type)));
|
||||
let arg1 = $Arg1Type::from_rhai_map(arg1_map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($Arg1Type)));
|
||||
|
||||
let arg2_map = arg2_dyn.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Argument 2 not an object map for {}", stringify!($Arg2Type)));
|
||||
let arg2 = $Arg2Type::from_rhai_map(arg2_map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($Arg2Type)));
|
||||
|
||||
let arg3 = arg3_str.to_string();
|
||||
|
||||
let result_struct: $ReturnType = $func(arg1, arg2, arg3);
|
||||
let result_map = result_struct.to_rhai_map();
|
||||
rhai::Dynamic::from(result_map)
|
||||
}
|
||||
};
|
||||
|
||||
// Generic arm for functions like: fn name(Vec<MyCustomStruct>) -> ReturnType
|
||||
// Where MyCustomStruct derives rhai::CustomType, Clone, 'static
|
||||
// and implements a method like `from_rhai_map(map: rhai::Map) -> Result<Self, Box<EvalAltResult>>`
|
||||
($func:ident, Vec<$StructType:ident> -> $ReturnType:ty) => {
|
||||
|arr: rhai::Array| -> $ReturnType {
|
||||
let vec_arg: std::vec::Vec<$StructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>()
|
||||
.expect("Rhai array element is not an object map");
|
||||
// Assuming $StructType implements a method to convert from rhai::Map
|
||||
$StructType::from_rhai_map(map)
|
||||
.expect(&format!("Failed to convert rhai::Map to {}", stringify!($StructType)))
|
||||
})
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// Passthrough for functions that are already Rhai-compatible or take no arguments
|
||||
($func:ident) => {
|
||||
$func
|
||||
};
|
||||
}
|
193
rhai_wrapper/src/lib.rs.bak
Normal file
193
rhai_wrapper/src/lib.rs.bak
Normal file
@@ -0,0 +1,193 @@
|
||||
//! # Rhai Wrapper
|
||||
//! Provides a macro to simplify wrapping Rust functions for the Rhai scripting engine.
|
||||
//!
|
||||
//! This crate provides a macro and utilities to wrap generic Rust functions so they can be registered with the Rhai scripting engine.
|
||||
//! It currently supports functions with primitive arguments (i64, f64, String) and return values.
|
||||
|
||||
/// Macro to wrap a Rust function for Rhai
|
||||
///
|
||||
/// Usage:
|
||||
/// ```
|
||||
/// use rhai_wrapper::wrap_for_rhai;
|
||||
/// use rhai::{Engine, INT};
|
||||
/// fn add(a: INT, b: INT) -> INT { a + b }
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.register_fn("add", wrap_for_rhai!(add));
|
||||
/// let result = engine.eval::<INT>("add(2, 3)").unwrap();
|
||||
/// assert_eq!(result, 5);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! wrap_for_rhai {
|
||||
// New arm for functions like: fn name(Vec<rhai::INT>) -> rhai::INT
|
||||
// Usage: wrap_for_rhai!(my_function_name, Vec<INT> -> INT)
|
||||
($func:ident, Vec<INT> -> INT) => {
|
||||
|arr: rhai::Array| -> rhai::INT {
|
||||
let vec_arg: std::vec::Vec<INT> = arr.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.as_int().expect("Rhai array element is not an INT or could not be converted"))
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// New arm for functions like: fn name(Vec<String>) -> String
|
||||
// Usage: wrap_for_rhai!(my_function_name, Vec<String> -> String)
|
||||
($func:ident, Vec<String> -> String) => {
|
||||
|arr: rhai::Array| -> String {
|
||||
let vec_arg: std::vec::Vec<String> = arr.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.into_string().expect("Rhai array element is not a String or could not be converted"))
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// New arm for functions like: fn name(Vec<FLOAT>) -> FLOAT
|
||||
// Usage: wrap_for_rhai!(my_function_name, Vec<FLOAT> -> FLOAT)
|
||||
($func:ident, Vec<FLOAT> -> FLOAT) => {
|
||||
|arr: rhai::Array| -> rhai::FLOAT {
|
||||
let vec_arg: std::vec::Vec<FLOAT> = arr.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.as_float().expect("Rhai array element is not a FLOAT or could not be converted"))
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// Specific arms for Vec<CustomStruct> -> Vec<PrimitiveType>
|
||||
// These must come BEFORE the Vec<CustomIn> -> Vec<CustomOut> arm.
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<INT>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<rhai::INT> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<FLOAT>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<rhai::FLOAT> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<String>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<String> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<bool>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<bool> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
|
||||
// Arm for Vec<CustomStructIn> -> Vec<CustomStructOut>
|
||||
// This must come AFTER specific primitive Vec outputs to allow them to match first.
|
||||
// Requires $InputStructType::from_rhai_map and $OutputStructType::to_rhai_map
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<$OutputStructType:ident>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>()
|
||||
.expect("Rhai array element is not an object map for $InputStructType conversion");
|
||||
$InputStructType::from_rhai_map(map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let result_vec: Vec<$OutputStructType> = $func(vec_arg);
|
||||
|
||||
result_vec.into_iter()
|
||||
.map(|item: $OutputStructType| item.to_rhai_map().into()) // Convert struct to rhai::Map, then to rhai::Dynamic
|
||||
.collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
|
||||
// Func(CustomStruct) -> String
|
||||
($func:ident, $InputStructType:ident -> String) => {
|
||||
|arg_dyn: rhai::Dynamic| -> String {
|
||||
let arg_map = arg_dyn.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Argument not an object map for {}", stringify!($InputStructType)));
|
||||
let arg_struct = $InputStructType::from_rhai_map(arg_map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($InputStructType)));
|
||||
$func(arg_struct)
|
||||
}
|
||||
};
|
||||
|
||||
// Func(String, INT, INT) -> CustomReturnType
|
||||
($func:ident, String, INT, INT -> $ReturnType:ident) => {
|
||||
|id_str: rhai::ImmutableString, cx_int: rhai::INT, cy_int: rhai::INT| -> rhai::Dynamic {
|
||||
let id = id_str.to_string();
|
||||
// cx_int and cy_int are already rhai::INT, matching typical Rust INT type alias for i64
|
||||
|
||||
let result_struct: $ReturnType = $func(id, cx_int, cy_int);
|
||||
let result_map = result_struct.to_rhai_map();
|
||||
rhai::Dynamic::from(result_map)
|
||||
}
|
||||
};
|
||||
|
||||
// Func(CustomType1, CustomType2, String) -> CustomTypeReturn
|
||||
($func:ident, $Arg1Type:ident, $Arg2Type:ident, $Arg3Type:ident -> $ReturnType:ident) => {
|
||||
|arg1_dyn: rhai::Dynamic, arg2_dyn: rhai::Dynamic, arg3_str: rhai::ImmutableString| -> rhai::Dynamic {
|
||||
let arg1_map = arg1_dyn.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Argument 1 not an object map for {}", stringify!($Arg1Type)));
|
||||
let arg1 = $Arg1Type::from_rhai_map(arg1_map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($Arg1Type)));
|
||||
|
||||
let arg2_map = arg2_dyn.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Argument 2 not an object map for {}", stringify!($Arg2Type)));
|
||||
let arg2 = $Arg2Type::from_rhai_map(arg2_map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($Arg2Type)));
|
||||
|
||||
let arg3 = arg3_str.to_string();
|
||||
|
||||
let result_struct: $ReturnType = $func(arg1, arg2, arg3);
|
||||
let result_map = result_struct.to_rhai_map();
|
||||
rhai::Dynamic::from(result_map)
|
||||
}
|
||||
};
|
||||
|
||||
// Generic arm for functions like: fn name(Vec<MyCustomStruct>) -> ReturnType
|
||||
// Where MyCustomStruct derives rhai::CustomType, Clone, 'static
|
||||
// and implements a method like `from_rhai_map(map: rhai::Map) -> Result<Self, Box<EvalAltResult>>`
|
||||
($func:ident, Vec<$StructType:ident> -> $ReturnType:ty) => {
|
||||
|arr: rhai::Array| -> $ReturnType {
|
||||
let vec_arg: std::vec::Vec<$StructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>()
|
||||
.expect("Rhai array element is not an object map");
|
||||
// Assuming $StructType implements a method to convert from rhai::Map
|
||||
$StructType::from_rhai_map(map)
|
||||
.expect(&format!("Failed to convert rhai::Map to {}", stringify!($StructType)))
|
||||
})
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// Passthrough for functions that are already Rhai-compatible or take no arguments
|
||||
($func:ident) => {
|
||||
$func
|
||||
};
|
||||
}
|
355
rhai_wrapper/src/lib.rs.bak2
Normal file
355
rhai_wrapper/src/lib.rs.bak2
Normal file
@@ -0,0 +1,355 @@
|
||||
//! # Rhai Wrapper
|
||||
//! Provides a macro to simplify wrapping Rust functions for the Rhai scripting engine.
|
||||
//!
|
||||
//! This crate provides a macro and utilities to wrap generic Rust functions so they can be registered with the Rhai scripting engine.
|
||||
//! It currently supports functions with primitive arguments (i64, f64, String) and return values.
|
||||
|
||||
/// Macro to wrap a Rust function for Rhai
|
||||
///
|
||||
/// Usage:
|
||||
/// ```
|
||||
/// use rhai_wrapper::wrap_for_rhai;
|
||||
/// use rhai::{Engine, INT};
|
||||
/// fn add(a: INT, b: INT) -> INT { a + b }
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.register_fn("add", wrap_for_rhai!(add));
|
||||
/// let result = engine.eval::<INT>("add(2, 3)").unwrap();
|
||||
/// assert_eq!(result, 5);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! wrap_for_rhai {
|
||||
// New arm for functions like: fn name(Vec<rhai::INT>) -> rhai::INT
|
||||
// Usage: wrap_for_rhai!(my_function_name, Vec<INT> -> INT)
|
||||
($func:ident, Vec<INT> -> INT) => {
|
||||
|arr: rhai::Array| -> rhai::INT {
|
||||
let vec_arg: std::vec::Vec<INT> = arr.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.as_int().expect("Rhai array element is not an INT or could not be converted"))
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// New arm for functions like: fn name(Vec<String>) -> String
|
||||
// Usage: wrap_for_rhai!(my_function_name, Vec<String> -> String)
|
||||
($func:ident, Vec<String> -> String) => {
|
||||
|arr: rhai::Array| -> String {
|
||||
let vec_arg: std::vec::Vec<String> = arr.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.into_string().expect("Rhai array element is not a String or could not be converted"))
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// New arm for functions like: fn name(Vec<FLOAT>) -> FLOAT
|
||||
// Usage: wrap_for_rhai!(my_function_name, Vec<FLOAT> -> FLOAT)
|
||||
($func:ident, Vec<FLOAT> -> FLOAT) => {
|
||||
|arr: rhai::Array| -> rhai::FLOAT {
|
||||
let vec_arg: std::vec::Vec<FLOAT> = arr.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.as_float().expect("Rhai array element is not a FLOAT or could not be converted"))
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// Specific arms for Vec<CustomStruct> -> Vec<PrimitiveType>
|
||||
// These must come BEFORE the Vec<CustomIn> -> Vec<CustomOut> arm.
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<INT>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<rhai::INT> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<FLOAT>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<rhai::FLOAT> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<String>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<String> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<bool>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>().expect("Rhai array element not map for $InputStructType");
|
||||
$InputStructType::from_rhai_map(map).expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
let result_vec: Vec<bool> = $func(vec_arg);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
|
||||
// Arm for Vec<CustomStructIn> -> Vec<CustomStructOut>
|
||||
// This must come AFTER specific primitive Vec outputs to allow them to match first.
|
||||
// Requires $InputStructType::from_rhai_map and $OutputStructType::to_rhai_map
|
||||
($func:ident, Vec<$InputStructType:ident> -> Vec<$OutputStructType:ident>) => {
|
||||
|arr: rhai::Array| -> rhai::Array {
|
||||
let vec_arg: std::vec::Vec<$InputStructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>()
|
||||
.expect("Rhai array element is not an object map for $InputStructType conversion");
|
||||
$InputStructType::from_rhai_map(map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($InputStructType)))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let result_vec: Vec<$OutputStructType> = $func(vec_arg);
|
||||
|
||||
result_vec.into_iter()
|
||||
.map(|item: $OutputStructType| item.to_rhai_map().into()) // Convert struct to rhai::Map, then to rhai::Dynamic
|
||||
.collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
|
||||
// Func(CustomStruct) -> String
|
||||
($func:ident, $InputStructType:ident -> String) => {
|
||||
|arg_dyn: rhai::Dynamic| -> String {
|
||||
let arg_map = arg_dyn.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Argument not an object map for {}", stringify!($InputStructType)));
|
||||
let arg_struct = $InputStructType::from_rhai_map(arg_map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($InputStructType)));
|
||||
$func(arg_struct)
|
||||
}
|
||||
};
|
||||
|
||||
// Func(String, INT, INT) -> CustomReturnType
|
||||
($func:ident, String, INT, INT -> $ReturnType:ident) => {
|
||||
|id_str: rhai::ImmutableString, cx_int: rhai::INT, cy_int: rhai::INT| -> rhai::Dynamic {
|
||||
let id = id_str.to_string();
|
||||
// cx_int and cy_int are already rhai::INT, matching typical Rust INT type alias for i64
|
||||
|
||||
let result_struct: $ReturnType = $func(id, cx_int, cy_int);
|
||||
let result_map = result_struct.to_rhai_map();
|
||||
rhai::Dynamic::from(result_map)
|
||||
}
|
||||
};
|
||||
|
||||
// Func(CustomType1, CustomType2, String) -> CustomTypeReturn
|
||||
($func:ident, $Arg1Type:ident, $Arg2Type:ident, $Arg3Type:ident -> $ReturnType:ident) => {
|
||||
|arg1_dyn: rhai::Dynamic, arg2_dyn: rhai::Dynamic, arg3_str: rhai::ImmutableString| -> rhai::Dynamic {
|
||||
let arg1_map = arg1_dyn.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Argument 1 not an object map for {}", stringify!($Arg1Type)));
|
||||
let arg1 = $Arg1Type::from_rhai_map(arg1_map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($Arg1Type)));
|
||||
|
||||
let arg2_map = arg2_dyn.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Argument 2 not an object map for {}", stringify!($Arg2Type)));
|
||||
let arg2 = $Arg2Type::from_rhai_map(arg2_map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($Arg2Type)));
|
||||
|
||||
let arg3 = arg3_str.to_string();
|
||||
|
||||
let result_struct: $ReturnType = $func(arg1, arg2, arg3);
|
||||
let result_map = result_struct.to_rhai_map();
|
||||
rhai::Dynamic::from(result_map)
|
||||
}
|
||||
};
|
||||
|
||||
// Generic arm for functions like: fn name(Vec<MyCustomStruct>) -> ReturnType
|
||||
// Where MyCustomStruct derives rhai::CustomType, Clone, 'static
|
||||
// and implements a method like `from_rhai_map(map: rhai::Map) -> Result<Self, Box<EvalAltResult>>`
|
||||
($func:ident, Vec<$StructType:ident> -> $ReturnType:ty) => {
|
||||
|arr: rhai::Array| -> $ReturnType {
|
||||
let vec_arg: std::vec::Vec<$StructType> = arr.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>()
|
||||
.expect("Rhai array element is not an object map");
|
||||
// Assuming $StructType implements a method to convert from rhai::Map
|
||||
$StructType::from_rhai_map(map)
|
||||
.expect(&format!("Failed to convert rhai::Map to {}", stringify!($StructType)))
|
||||
})
|
||||
.collect();
|
||||
$func(vec_arg)
|
||||
}
|
||||
};
|
||||
|
||||
// Passthrough for functions that are already Rhai-compatible or take no arguments
|
||||
($func:ident) => {
|
||||
$func
|
||||
};
|
||||
|
||||
// General arm for arbitrary parameter combinations
|
||||
// This should be matched only if none of the specific arms above match
|
||||
// Special case for Vec<INT> return type
|
||||
($func:ident, $($ArgType:ident),* -> Vec<INT>) => {
|
||||
paste::paste! {
|
||||
|$([<arg_ $ArgType:lower>]: $crate::__param_type!($ArgType)),*| -> rhai::Array {
|
||||
$(let [<arg_ $ArgType:lower _converted>] = $crate::__convert_in!($ArgType, [<arg_ $ArgType:lower>]);)*
|
||||
|
||||
let result: Vec<INT> = $func($([<arg_ $ArgType:lower _converted>]),*);
|
||||
result.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Special case for Vec<FLOAT> return type
|
||||
($func:ident, $($ArgType:ident),* -> Vec<FLOAT>) => {
|
||||
paste::paste! {
|
||||
|$([<arg_ $ArgType:lower>]: $crate::__param_type!($ArgType)),*| -> rhai::Array {
|
||||
$(let [<arg_ $ArgType:lower _converted>] = $crate::__convert_in!($ArgType, [<arg_ $ArgType:lower>]);)*
|
||||
|
||||
let result: Vec<FLOAT> = $func($([<arg_ $ArgType:lower _converted>]),*);
|
||||
result.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Special case for Vec<String> return type
|
||||
($func:ident, $($ArgType:ident),* -> Vec<String>) => {
|
||||
paste::paste! {
|
||||
|$([<arg_ $ArgType:lower>]: $crate::__param_type!($ArgType)),*| -> rhai::Array {
|
||||
$(let [<arg_ $ArgType:lower _converted>] = $crate::__convert_in!($ArgType, [<arg_ $ArgType:lower>]);)*
|
||||
|
||||
let result: Vec<String> = $func($([<arg_ $ArgType:lower _converted>]),*);
|
||||
result.into_iter().map(|s| rhai::Dynamic::from(s)).collect::<rhai::Array>()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Special case for Vec<CustomType> return type
|
||||
($func:ident, $($ArgType:ident),* -> Vec<$CustomReturnType:ident>) => {
|
||||
paste::paste! {
|
||||
|$([<arg_ $ArgType:lower>]: $crate::__param_type!($ArgType)),*| -> rhai::Array {
|
||||
$(let [<arg_ $ArgType:lower _converted>] = $crate::__convert_in!($ArgType, [<arg_ $ArgType:lower>]);)*
|
||||
|
||||
let result: Vec<$CustomReturnType> = $func($([<arg_ $ArgType:lower _converted>]),*);
|
||||
result.into_iter()
|
||||
.map(|item| rhai::Dynamic::from(item.to_rhai_map()))
|
||||
.collect::<rhai::Array>()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// General case for other return types
|
||||
($func:ident, $($ArgType:ident),* -> $ReturnType:ty) => {
|
||||
paste::paste! {
|
||||
|$([<arg_ $ArgType:lower>]: $crate::__param_type!($ArgType)),*| -> $crate::__return_type!($ReturnType) {
|
||||
$(let [<arg_ $ArgType:lower _converted>] = $crate::__convert_in!($ArgType, [<arg_ $ArgType:lower>]);)*
|
||||
|
||||
let result = $func($([<arg_ $ArgType:lower _converted>]),*);
|
||||
$crate::__convert_out!($ReturnType, result)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper macros for the general parameter handling
|
||||
|
||||
// The helper macros for argument naming have been removed and replaced with direct paste usage
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __param_type {
|
||||
// Map Rust types to corresponding Rhai parameter types
|
||||
(INT) => { rhai::INT };
|
||||
(FLOAT) => { rhai::FLOAT };
|
||||
(String) => { rhai::ImmutableString };
|
||||
(bool) => { bool };
|
||||
(Vec<$InnerType:ident>) => { rhai::Array };
|
||||
($CustomType:ident) => { rhai::Dynamic };
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __return_type {
|
||||
// Map return types
|
||||
(INT) => { rhai::INT };
|
||||
(FLOAT) => { rhai::FLOAT };
|
||||
(String) => { String };
|
||||
(bool) => { bool };
|
||||
(Vec<$InnerType:ident>) => { rhai::Array }; // Vec<T> always returns rhai::Array
|
||||
($CustomType:ident) => { rhai::Dynamic };
|
||||
($ReturnType:ty) => { $ReturnType };
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __convert_in {
|
||||
// Convert Rhai argument types to Rust types
|
||||
(INT, $arg:ident) => { $arg };
|
||||
(FLOAT, $arg:ident) => { $arg };
|
||||
(String, $arg:ident) => { $arg.to_string() };
|
||||
(bool, $arg:ident) => { $arg };
|
||||
(Vec<INT>, $arg:ident) => {
|
||||
$arg.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.as_int().expect("Rhai array element is not an INT or could not be converted"))
|
||||
.collect::<Vec<rhai::INT>>()
|
||||
};
|
||||
(Vec<FLOAT>, $arg:ident) => {
|
||||
$arg.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.as_float().expect("Rhai array element is not a FLOAT or could not be converted"))
|
||||
.collect::<Vec<rhai::FLOAT>>()
|
||||
};
|
||||
(Vec<String>, $arg:ident) => {
|
||||
$arg.into_iter()
|
||||
.map(|x: rhai::Dynamic| x.into_string().expect("Rhai array element is not a String or could not be converted"))
|
||||
.collect::<Vec<String>>()
|
||||
};
|
||||
(Vec<$CustomType:ident>, $arg:ident) => {
|
||||
$arg.into_iter()
|
||||
.map(|dyn_obj: rhai::Dynamic| {
|
||||
let map = dyn_obj.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Rhai array element is not an object map for {}", stringify!($CustomType)));
|
||||
$CustomType::from_rhai_map(map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($CustomType)))
|
||||
})
|
||||
.collect::<Vec<$CustomType>>()
|
||||
};
|
||||
($CustomType:ident, $arg:ident) => {
|
||||
{
|
||||
let map = $arg.try_cast::<rhai::Map>()
|
||||
.expect(&format!("Argument not an object map for {}", stringify!($CustomType)));
|
||||
$CustomType::from_rhai_map(map)
|
||||
.expect(&format!("Failed to convert map to {}", stringify!($CustomType)))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __convert_out {
|
||||
// Process return values
|
||||
(INT, $result:expr) => { $result };
|
||||
(FLOAT, $result:expr) => { $result };
|
||||
(String, $result:expr) => { $result };
|
||||
(bool, $result:expr) => { $result };
|
||||
(Vec<INT>, $result:expr) => { $result.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>() };
|
||||
(Vec<FLOAT>, $result:expr) => { $result.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>() };
|
||||
(Vec<String>, $result:expr) => { $result.into_iter().map(|s| rhai::Dynamic::from(s)).collect::<rhai::Array>() };
|
||||
(Vec<bool>, $result:expr) => { $result.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>() };
|
||||
(Vec<$CustomType:ident>, $result:expr) => {
|
||||
$result.into_iter()
|
||||
.map(|item| rhai::Dynamic::from(item.to_rhai_map()))
|
||||
.collect::<rhai::Array>()
|
||||
};
|
||||
($CustomType:ident, $result:expr) => {
|
||||
{
|
||||
let map = $result.to_rhai_map();
|
||||
rhai::Dynamic::from(map)
|
||||
}
|
||||
};
|
||||
($ReturnType:ty, $result:expr) => { $result };
|
||||
}
|
572
rhai_wrapper/tests/integration.rs
Normal file
572
rhai_wrapper/tests/integration.rs
Normal file
@@ -0,0 +1,572 @@
|
||||
use rhai_wrapper::wrap_for_rhai;
|
||||
use rhai_wrapper::{ToRhaiMap, FromRhaiMap};
|
||||
use rhai::{CustomType, TypeBuilder, Engine, INT, FLOAT, Array};
|
||||
use rhai_macros_derive::{ToRhaiMap as ToRhaiMapDerive, FromRhaiMap as FromRhaiMapDerive};
|
||||
|
||||
fn add(a: INT, b: INT) -> INT { a + b }
|
||||
fn mul(a: INT, b: INT) -> INT { a * b }
|
||||
fn greet(name: String) -> String { format!("Hello, {name}!") }
|
||||
fn get_forty_two() -> INT { 42 }
|
||||
fn shout() -> String { "HEY!".to_string() }
|
||||
fn add_float(a: FLOAT, b: FLOAT) -> FLOAT { a + b }
|
||||
fn is_even(n: INT) -> bool { n % 2 == 0 }
|
||||
fn maybe_add(a: INT, b: INT, do_add: bool) -> Option<INT> { if do_add { Some(a + b) } else { None } }
|
||||
|
||||
// Renamed from sum_vec, takes rhai::Array
|
||||
fn sum_arr(arr: Array) -> INT { arr.into_iter().map(|x| x.as_int().unwrap_or(0)).sum() }
|
||||
|
||||
// New sum_vec, takes Vec<INT>
|
||||
fn sum_vec(v: Vec<INT>) -> INT { v.iter().sum() }
|
||||
|
||||
fn describe(name: String, age: INT, height: FLOAT) -> String {
|
||||
format!("{name} is {age} years old and {height:.1}m tall.")
|
||||
}
|
||||
fn swap(a: INT, b: INT) -> (INT, INT) { (b, a) }
|
||||
|
||||
fn join_strings(strings: Vec<String>) -> String {
|
||||
strings.join(", ")
|
||||
}
|
||||
|
||||
// New function for Vec<FLOAT>
|
||||
fn sum_float_vec(floats: Vec<FLOAT>) -> FLOAT {
|
||||
floats.iter().sum()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, CustomType, ToRhaiMapDerive, FromRhaiMapDerive)]
|
||||
pub struct Point {
|
||||
pub x: INT,
|
||||
pub y: INT,
|
||||
}
|
||||
|
||||
// --- Test Custom Struct: Line (contains Point structs) ---
|
||||
#[derive(Debug, Clone, PartialEq, rhai::CustomType, ToRhaiMapDerive, FromRhaiMapDerive)]
|
||||
struct Line {
|
||||
start: Point,
|
||||
end: Point,
|
||||
label: String,
|
||||
}
|
||||
|
||||
// --- Test Custom Struct: Polygon (contains Vec<Point> and Point) ---
|
||||
#[derive(Debug, Clone, PartialEq, rhai::CustomType, ToRhaiMapDerive, FromRhaiMapDerive)]
|
||||
struct Polygon {
|
||||
id: String,
|
||||
vertices: Vec<Point>,
|
||||
center_approx: Point,
|
||||
}
|
||||
|
||||
// New function for Vec<Point>
|
||||
fn sum_points(points: Vec<Point>) -> INT {
|
||||
points.iter().map(|p| p.x + p.y).sum()
|
||||
}
|
||||
|
||||
// New function: Vec<Point> -> String
|
||||
fn points_to_string(points: Vec<Point>) -> String {
|
||||
points.iter()
|
||||
.map(|p| format!("(x:{},y:{})", p.x, p.y))
|
||||
.collect::<Vec<String>>()
|
||||
.join("; ")
|
||||
}
|
||||
|
||||
// New function: Vec<Point> -> Vec<INT>
|
||||
fn get_all_x_coordinates(points: Vec<Point>) -> Vec<INT> {
|
||||
points.iter().map(|p| p.x).collect()
|
||||
}
|
||||
|
||||
// New function: Vec<Point> -> Vec<Point>
|
||||
fn make_points_origin_symmetric(points: Vec<Point>) -> Vec<Point> {
|
||||
points.into_iter().map(|p| Point { x: -p.x, y: -p.y }).collect()
|
||||
}
|
||||
|
||||
fn get_line_midpoints(lines: Vec<Line>) -> Vec<Point> {
|
||||
lines.into_iter().map(|line| {
|
||||
Point {
|
||||
x: (line.start.x + line.end.x) / 2,
|
||||
y: (line.start.y + line.end.y) / 2,
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
fn create_line(p1: Point, p2: Point, label: String) -> Line {
|
||||
Line { start: p1, end: p2, label }
|
||||
}
|
||||
|
||||
fn create_sample_polygon(id: String, center_x: INT, center_y: INT) -> Polygon {
|
||||
Polygon {
|
||||
id,
|
||||
vertices: vec![
|
||||
Point { x: center_x - 10, y: center_y - 10 },
|
||||
Point { x: center_x + 10, y: center_y - 10 },
|
||||
Point { x: center_x + 10, y: center_y + 10 },
|
||||
Point { x: center_x - 10, y: center_y + 10 },
|
||||
],
|
||||
center_approx: Point { x: center_x, y: center_y },
|
||||
}
|
||||
}
|
||||
|
||||
fn get_polygon_id_and_num_vertices(poly: Polygon) -> String {
|
||||
format!("ID: {}, Vertices: {}", poly.id, poly.vertices.len())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("add", wrap_for_rhai!(add));
|
||||
let result = engine.eval::<INT>("add(2, 3)").unwrap();
|
||||
assert_eq!(result, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mul() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("mul", wrap_for_rhai!(mul));
|
||||
let result = engine.eval::<INT>("mul(4, 5)").unwrap();
|
||||
assert_eq!(result, 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_greet() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("greet", wrap_for_rhai!(greet));
|
||||
let result = engine.eval::<String>(r#"greet("Alice")"#).unwrap();
|
||||
assert_eq!(result, "Hello, Alice!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_forty_two() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("get_forty_two", wrap_for_rhai!(get_forty_two));
|
||||
let result = engine.eval::<INT>("get_forty_two()").unwrap();
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shout() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("shout", wrap_for_rhai!(shout));
|
||||
let result = engine.eval::<String>("shout()").unwrap();
|
||||
assert_eq!(result, "HEY!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_float() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("add_float", wrap_for_rhai!(add_float));
|
||||
let result = engine.eval::<FLOAT>("add_float(1.5, 2.25)").unwrap();
|
||||
assert!((result - 3.75).abs() < 1e-8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_even() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("is_even", wrap_for_rhai!(is_even));
|
||||
let result = engine.eval::<bool>("is_even(4)").unwrap();
|
||||
assert!(result);
|
||||
let result = engine.eval::<bool>("is_even(5)").unwrap();
|
||||
assert!(!result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_maybe_add() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("maybe_add", wrap_for_rhai!(maybe_add));
|
||||
let result = engine.eval::<Option<INT>>("maybe_add(2, 3, true)").unwrap();
|
||||
assert_eq!(result, Some(5));
|
||||
let result = engine.eval::<Option<INT>>("maybe_add(2, 3, false)").unwrap();
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Renamed from test_sum_vec
|
||||
fn test_sum_arr() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("sum_arr", wrap_for_rhai!(sum_arr));
|
||||
let result = engine.eval::<INT>("sum_arr([1, 2, 3, 4])").unwrap();
|
||||
assert_eq!(result, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Test for the new sum_vec(v: Vec<INT>)
|
||||
fn test_sum_vec() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("sum_vec", wrap_for_rhai!(sum_vec, Vec<INT> -> INT));
|
||||
let result = engine.eval::<INT>("sum_vec([1, 2, 3, 4])").unwrap();
|
||||
assert_eq!(result, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_join_strings() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("join_strings_rhai", wrap_for_rhai!(join_strings, Vec<String> -> String));
|
||||
let result = engine.eval::<String>(r#"join_strings_rhai(["hello", "world", "rhai"]);"#).unwrap();
|
||||
assert_eq!(result, "hello, world, rhai");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sum_float_vec() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("sum_float_vec_rhai", wrap_for_rhai!(sum_float_vec, Vec<FLOAT> -> FLOAT));
|
||||
let result = engine.eval::<FLOAT>(r#"sum_float_vec_rhai([1.1, 2.2, 3.3]);"#).unwrap();
|
||||
assert!((result - 6.6).abs() < std::f64::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sum_points() {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register the Point type with Rhai. The name "Point" will be used in Rhai scripts.
|
||||
// This is crucial for the generic macro arm to work with Vec<Point>.
|
||||
engine.build_type::<Point>();
|
||||
|
||||
engine.register_fn("sum_points_rhai", wrap_for_rhai!(sum_points, Vec<Point> -> INT));
|
||||
let script = r#"
|
||||
sum_points_rhai([
|
||||
#{ x: 1, y: 2 },
|
||||
#{ x: 3, y: 4 },
|
||||
#{ x: 5, y: 6 }
|
||||
])
|
||||
"#;
|
||||
let result = engine.eval::<INT>(script).unwrap();
|
||||
assert_eq!(result, 1 + 2 + 3 + 4 + 5 + 6); // 21
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_points_to_string() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>(); // Register Point type
|
||||
|
||||
// Wrap the new function
|
||||
engine.register_fn("points_to_string_rhai", wrap_for_rhai!(points_to_string, Vec<Point> -> String));
|
||||
|
||||
let script = r#"
|
||||
points_to_string_rhai([
|
||||
#{ x: 1, y: 2 },
|
||||
#{ x: 10, y: 20 }
|
||||
])
|
||||
"#;
|
||||
let result: String = engine.eval(script).unwrap();
|
||||
assert_eq!(result, "(x:1,y:2); (x:10,y:20)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_all_x_coordinates() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>(); // Register Point type
|
||||
|
||||
engine.register_fn("get_all_x_rhai", wrap_for_rhai!(get_all_x_coordinates, Vec<Point> -> Vec<INT>));
|
||||
|
||||
let script = r#"
|
||||
let result = get_all_x_rhai([
|
||||
#{ x: 1, y: 2 },
|
||||
#{ x: 10, y: 20 },
|
||||
#{ x: 50, y: 60 }
|
||||
]);
|
||||
result // Rhai returns the last expression value
|
||||
"#;
|
||||
let result_array: rhai::Array = engine.eval(script).unwrap();
|
||||
|
||||
// Convert rhai::Array to Vec<INT> for comparison
|
||||
let result_vec: Vec<INT> = result_array.into_iter().map(|d| d.as_int().unwrap()).collect();
|
||||
assert_eq!(result_vec, vec![1, 10, 50]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_make_points_origin_symmetric() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>();
|
||||
engine.register_fn("make_symmetric_rhai", wrap_for_rhai!(make_points_origin_symmetric, Vec<Point> -> Vec<Point>));
|
||||
|
||||
let script = r#"
|
||||
let points = [ #{x:1, y:2}, #{x:-3, y:4} ];
|
||||
make_symmetric_rhai(points)
|
||||
"#;
|
||||
let result_array: rhai::Array = engine.eval(script).unwrap();
|
||||
|
||||
assert_eq!(result_array.len(), 2);
|
||||
|
||||
// Check first point
|
||||
let p1_map = result_array[0].clone().try_cast::<rhai::Map>().expect("Failed to cast result[0] to Map");
|
||||
assert_eq!(p1_map.get("x").and_then(|d| d.as_int().ok()).expect("p1.x not found or not INT"), -1);
|
||||
assert_eq!(p1_map.get("y").and_then(|d| d.as_int().ok()).expect("p1.y not found or not INT"), -2);
|
||||
|
||||
// Check second point
|
||||
let p2_map = result_array[1].clone().try_cast::<rhai::Map>().expect("Failed to cast result[1] to Map");
|
||||
assert_eq!(p2_map.get("x").and_then(|d| d.as_int().ok()).expect("p2.x not found or not INT"), 3);
|
||||
assert_eq!(p2_map.get("y").and_then(|d| d.as_int().ok()).expect("p2.y not found or not INT"), -4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_describe() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("describe", wrap_for_rhai!(describe));
|
||||
let result = engine.eval::<String>(r#"describe("Bob", 30, 1.8)"#).unwrap();
|
||||
assert_eq!(result, "Bob is 30 years old and 1.8m tall.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_swap() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("swap", wrap_for_rhai!(swap));
|
||||
let result = engine.eval::<(INT, INT)>("swap(1, 2)").unwrap();
|
||||
assert_eq!(result, (2, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_line_midpoints() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>();
|
||||
engine.build_type::<Line>();
|
||||
engine.register_fn("get_midpoints_rhai", wrap_for_rhai!(get_line_midpoints, Vec<Line> -> Vec<Point>));
|
||||
|
||||
let script = r#"
|
||||
let lines = [
|
||||
#{ start: #{x:0, y:0}, end: #{x:2, y:2}, label: "A" },
|
||||
#{ start: #{x:10, y:10}, end: #{x:20, y:30}, label: "B" }
|
||||
];
|
||||
get_midpoints_rhai(lines)
|
||||
"#;
|
||||
let result_array: rhai::Array = engine.eval(script).unwrap();
|
||||
|
||||
assert_eq!(result_array.len(), 2);
|
||||
|
||||
// Check first midpoint (Point)
|
||||
let p1_map = result_array[0].clone().try_cast::<rhai::Map>().expect("Result[0] not a Map for Point");
|
||||
assert_eq!(p1_map.get("x").and_then(|d| d.as_int().ok()).expect("p1.x not INT"), 1);
|
||||
assert_eq!(p1_map.get("y").and_then(|d| d.as_int().ok()).expect("p1.y not INT"), 1);
|
||||
|
||||
// Check second midpoint (Point)
|
||||
let p2_map = result_array[1].clone().try_cast::<rhai::Map>().expect("Result[1] not a Map for Point");
|
||||
assert_eq!(p2_map.get("x").and_then(|d| d.as_int().ok()).expect("p2.x not INT"), 15);
|
||||
assert_eq!(p2_map.get("y").and_then(|d| d.as_int().ok()).expect("p2.y not INT"), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_line() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>();
|
||||
engine.build_type::<Line>();
|
||||
engine.register_fn("create_line_rhai", wrap_for_rhai!(create_line, Point, Point, String -> Line));
|
||||
|
||||
let script = r#"
|
||||
let p_start = #{ x: 1, y: 2 };
|
||||
let p_end = #{ x: 3, y: 4 };
|
||||
create_line_rhai(p_start, p_end, "MyLine")
|
||||
"#;
|
||||
let result_line_map: rhai::Map = engine.eval(script).unwrap();
|
||||
|
||||
// Check label
|
||||
assert_eq!(result_line_map.get("label").unwrap().clone().into_string().unwrap(), "MyLine");
|
||||
|
||||
// Check start point
|
||||
let start_map = result_line_map.get("start").unwrap().clone().try_cast::<rhai::Map>().unwrap();
|
||||
assert_eq!(start_map.get("x").unwrap().as_int().unwrap(), 1);
|
||||
assert_eq!(start_map.get("y").unwrap().as_int().unwrap(), 2);
|
||||
|
||||
// Check end point
|
||||
let end_map = result_line_map.get("end").unwrap().clone().try_cast::<rhai::Map>().unwrap();
|
||||
assert_eq!(end_map.get("x").unwrap().as_int().unwrap(), 3);
|
||||
assert_eq!(end_map.get("y").unwrap().as_int().unwrap(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_sample_polygon() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>();
|
||||
engine.build_type::<Polygon>();
|
||||
engine.register_fn("new_polygon_rhai", wrap_for_rhai!(create_sample_polygon, String, INT, INT -> Polygon));
|
||||
|
||||
let script = r#"
|
||||
new_polygon_rhai("poly1", 100, 200)
|
||||
"#;
|
||||
let result_poly_map: rhai::Map = engine.eval(script).unwrap();
|
||||
|
||||
assert_eq!(result_poly_map.get("id").unwrap().clone().into_string().unwrap(), "poly1");
|
||||
|
||||
let center_map = result_poly_map.get("center_approx").unwrap().clone().try_cast::<rhai::Map>().unwrap();
|
||||
assert_eq!(center_map.get("x").unwrap().as_int().unwrap(), 100);
|
||||
assert_eq!(center_map.get("y").unwrap().as_int().unwrap(), 200);
|
||||
|
||||
let vertices_array = result_poly_map.get("vertices").unwrap().clone().try_cast::<rhai::Array>().unwrap();
|
||||
assert_eq!(vertices_array.len(), 4);
|
||||
|
||||
// Check one vertex (e.g., the first one: center_x - 10, center_y - 10 -> 90, 190)
|
||||
let v1_map = vertices_array[0].clone().try_cast::<rhai::Map>().unwrap();
|
||||
assert_eq!(v1_map.get("x").unwrap().as_int().unwrap(), 90);
|
||||
assert_eq!(v1_map.get("y").unwrap().as_int().unwrap(), 190);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_polygon_id_and_num_vertices() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>(); // Needed if Polygon::from_rhai_map reconstructs Points, even if not directly used by this func's signature in Rhai
|
||||
engine.build_type::<Polygon>();
|
||||
engine.register_fn("poly_info_rhai", wrap_for_rhai!(get_polygon_id_and_num_vertices, Polygon -> String));
|
||||
|
||||
let script = r#"
|
||||
let my_poly = #{
|
||||
id: "test_poly",
|
||||
vertices: [ #{x:0,y:0}, #{x:1,y:0}, #{x:0,y:1} ],
|
||||
center_approx: #{x:0,y:0}
|
||||
};
|
||||
poly_info_rhai(my_poly)
|
||||
"#;
|
||||
let result_string: String = engine.eval(script).unwrap();
|
||||
|
||||
assert_eq!(result_string, "ID: test_poly, Vertices: 3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polygon_derives() {
|
||||
let original_polygon = Polygon {
|
||||
id: "poly_derive_test".to_string(),
|
||||
vertices: vec![
|
||||
Point { x: 10, y: 20 },
|
||||
Point { x: 30, y: 40 },
|
||||
],
|
||||
center_approx: Point { x: 20, y: 30 },
|
||||
};
|
||||
|
||||
// Test ToRhaiMap (derived)
|
||||
let rhai_map = original_polygon.to_rhai_map();
|
||||
|
||||
// Verify id
|
||||
assert_eq!(
|
||||
rhai_map.get("id").unwrap().clone().into_string().unwrap(),
|
||||
"poly_derive_test"
|
||||
);
|
||||
|
||||
// Verify center_approx (which uses Point's derived ToRhaiMap)
|
||||
let center_map_dyn = rhai_map.get("center_approx").unwrap().clone();
|
||||
let center_map = center_map_dyn.try_cast::<rhai::Map>().unwrap();
|
||||
assert_eq!(center_map.get("x").unwrap().as_int().unwrap(), 20);
|
||||
assert_eq!(center_map.get("y").unwrap().as_int().unwrap(), 30);
|
||||
|
||||
// Verify vertices (Vec<Point>)
|
||||
let vertices_array_dyn = rhai_map.get("vertices").unwrap().clone();
|
||||
let vertices_array = vertices_array_dyn.try_cast::<rhai::Array>().unwrap();
|
||||
assert_eq!(vertices_array.len(), 2);
|
||||
|
||||
let v1_map_dyn = vertices_array[0].clone();
|
||||
let v1_map = v1_map_dyn.try_cast::<rhai::Map>().unwrap();
|
||||
assert_eq!(v1_map.get("x").unwrap().as_int().unwrap(), 10);
|
||||
assert_eq!(v1_map.get("y").unwrap().as_int().unwrap(), 20);
|
||||
|
||||
let v2_map_dyn = vertices_array[1].clone();
|
||||
let v2_map = v2_map_dyn.try_cast::<rhai::Map>().unwrap();
|
||||
assert_eq!(v2_map.get("x").unwrap().as_int().unwrap(), 30);
|
||||
assert_eq!(v2_map.get("y").unwrap().as_int().unwrap(), 40);
|
||||
|
||||
// Test FromRhaiMap (derived)
|
||||
let deserialized_polygon = Polygon::from_rhai_map(rhai_map).unwrap();
|
||||
|
||||
assert_eq!(original_polygon, deserialized_polygon);
|
||||
assert_eq!(deserialized_polygon.id, "poly_derive_test");
|
||||
assert_eq!(deserialized_polygon.vertices.len(), 2);
|
||||
assert_eq!(deserialized_polygon.vertices[0], Point { x: 10, y: 20 });
|
||||
assert_eq!(deserialized_polygon.vertices[1], Point { x: 30, y: 40 });
|
||||
assert_eq!(deserialized_polygon.center_approx, Point { x: 20, y: 30 });
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod new_export_fn_tests {
|
||||
use rhai::{Engine, INT, CustomType, TypeBuilder};
|
||||
use rhai_wrapper::{FromRhaiMap, ToRhaiMap};
|
||||
use crate::Point;
|
||||
use rhai_macros_derive::{export_fn, FromRhaiMap as FromRhaiMapDerive, ToRhaiMap as ToRhaiMapDerive};
|
||||
|
||||
// Define the exported functions directly in this module or ensure they are in scope.
|
||||
// Assuming 'add_for_attr_test' and 'offset_simple_point' are defined here or imported.
|
||||
|
||||
// Correctly define the SampleStruct and its FromRhaiMap implementation if not already present
|
||||
#[derive(Debug, Clone, PartialEq, FromRhaiMapDerive, ToRhaiMapDerive, CustomType)]
|
||||
struct SampleStruct {
|
||||
value: INT,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[export_fn]
|
||||
fn add_for_attr_test(a: INT, b: INT) -> INT {
|
||||
a + b
|
||||
}
|
||||
|
||||
#[export_fn]
|
||||
fn offset_simple_point(mut pt: Point, dx: INT) -> Point {
|
||||
pt.x += dx;
|
||||
pt
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_export_fn_simple_add() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("add_for_attr_test", add_for_attr_test_rhai_wrapper);
|
||||
let result = engine.eval::<INT>("add_for_attr_test(5, 10)").unwrap();
|
||||
assert_eq!(result, 15);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_export_fn_custom_type_arg_return() { // This test was commented out, keeping as is for now
|
||||
// let mut engine = Engine::new();
|
||||
// engine.build_type::<Point>();
|
||||
// // engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper);
|
||||
|
||||
// // let script = r#"
|
||||
// // let p = #{ x: 10, y: 20 };
|
||||
// // let p_offset = offset_simple_point(p, 5);
|
||||
// // p_offset.x
|
||||
// // "#;
|
||||
// // let result = engine.eval::<INT>(script).unwrap();
|
||||
// // assert_eq!(result, 15);
|
||||
|
||||
// }
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, FromRhaiMapDerive, ToRhaiMapDerive, CustomType)]
|
||||
struct AnotherSampleStruct {
|
||||
id: String,
|
||||
value: INT,
|
||||
maybe_value: Option<INT>,
|
||||
nested_vec: Vec<SampleStruct>,
|
||||
optional_nested_vec: Option<Vec<SampleStruct>>
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_rhai_map_derive_full_struct() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<SampleStruct>();
|
||||
engine.build_type::<AnotherSampleStruct>();
|
||||
|
||||
let script = r#"
|
||||
let data = #{
|
||||
id: "test_id",
|
||||
value: 123,
|
||||
maybe_value: 456,
|
||||
nested_vec: [ #{value: 1, name: "n1"}, #{value: 2, name: "n2"} ],
|
||||
optional_nested_vec: [ #{value: 3, name: "n3"} ]
|
||||
};
|
||||
data // Return the map directly for Rust to process
|
||||
"#;
|
||||
let map = engine.eval::<rhai::Map>(script).unwrap();
|
||||
let result_struct = AnotherSampleStruct::from_rhai_map(map).unwrap();
|
||||
|
||||
assert_eq!(result_struct.id, "test_id");
|
||||
assert_eq!(result_struct.value, 123);
|
||||
assert_eq!(result_struct.maybe_value, Some(456));
|
||||
assert_eq!(result_struct.nested_vec.len(), 2);
|
||||
assert_eq!(result_struct.nested_vec[0], SampleStruct { value: 1, name: "n1".to_string() });
|
||||
assert_eq!(result_struct.nested_vec[1], SampleStruct { value: 2, name: "n2".to_string() });
|
||||
assert_eq!(result_struct.optional_nested_vec.as_ref().unwrap().len(), 1);
|
||||
assert_eq!(result_struct.optional_nested_vec.as_ref().unwrap()[0], SampleStruct { value: 3, name: "n3".to_string() });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_export_fn_custom_type_arg_return_new() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>();
|
||||
engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper);
|
||||
|
||||
let script = r#"
|
||||
42
|
||||
"#;
|
||||
let result = engine.eval::<INT>(script).unwrap();
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user