..
This commit is contained in:
parent
2221f3f88c
commit
c7a7201cfd
File diff suppressed because it is too large
Load Diff
134
herodb/aiprompts/rhaiwrapping_advanced.md
Normal file
134
herodb/aiprompts/rhaiwrapping_advanced.md
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
|
||||||
|
### Error Handling in Dynamic Functions
|
||||||
|
|
||||||
|
When working with the dynamic function signature, error handling is slightly different:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn dynamic_function(ctx: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
// Get the position information from the context
|
||||||
|
let pos = ctx.position();
|
||||||
|
|
||||||
|
// Validate arguments
|
||||||
|
if args.len() < 2 {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||||
|
format!("Expected at least 2 arguments, got {}", args.len()),
|
||||||
|
pos
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to convert arguments with proper error handling
|
||||||
|
let arg1 = match args[0].as_int() {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
|
"Expected first argument to be an integer".into(),
|
||||||
|
pos,
|
||||||
|
"i64".into()
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Process with error handling
|
||||||
|
if arg1 <= 0 {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||||
|
"First argument must be positive".into(),
|
||||||
|
pos
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return success
|
||||||
|
Ok(Dynamic::from(arg1 * 2))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Advanced Patterns
|
||||||
|
|
||||||
|
### Working with Function Pointers
|
||||||
|
|
||||||
|
You can create function pointers that bind to Rust functions:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn my_awesome_fn(ctx: NativeCallContext, args: &mut[&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
// Check number of arguments
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err("one argument is required, plus the object".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get call arguments
|
||||||
|
let x = args[1].try_cast::<i64>().map_err(|_| "argument must be an integer".into())?;
|
||||||
|
|
||||||
|
// Get mutable reference to the object map, which is passed as the first argument
|
||||||
|
let map = &mut *args[0].as_map_mut().map_err(|_| "object must be a map".into())?;
|
||||||
|
|
||||||
|
// Do something awesome here ...
|
||||||
|
let result = x * 2;
|
||||||
|
|
||||||
|
Ok(result.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a function to create a pre-defined object
|
||||||
|
engine.register_fn("create_awesome_object", || {
|
||||||
|
// Use an object map as base
|
||||||
|
let mut map = Map::new();
|
||||||
|
|
||||||
|
// Create a function pointer that binds to 'my_awesome_fn'
|
||||||
|
let fp = FnPtr::from_fn("awesome", my_awesome_fn)?;
|
||||||
|
// ^ name of method
|
||||||
|
// ^ native function
|
||||||
|
|
||||||
|
// Store the function pointer in the object map
|
||||||
|
map.insert("awesome".into(), fp.into());
|
||||||
|
|
||||||
|
Ok(Dynamic::from_map(map))
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating Rust Closures from Rhai Functions
|
||||||
|
|
||||||
|
You can encapsulate a Rhai script as a Rust closure:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use rhai::{Engine, Func};
|
||||||
|
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
let script = "fn calc(x, y) { x + y.len < 42 }";
|
||||||
|
|
||||||
|
// Create a Rust closure from a Rhai function
|
||||||
|
let func = Func::<(i64, &str), bool>::create_from_script(
|
||||||
|
engine, // the 'Engine' is consumed into the closure
|
||||||
|
script, // the script
|
||||||
|
"calc" // the entry-point function name
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Call the closure
|
||||||
|
let result = func(123, "hello")?;
|
||||||
|
|
||||||
|
// Pass it as a callback to another function
|
||||||
|
schedule_callback(func);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Calling Rhai Functions from Rust
|
||||||
|
|
||||||
|
You can call Rhai functions from Rust:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Compile the script to AST
|
||||||
|
let ast = engine.compile(script)?;
|
||||||
|
|
||||||
|
// Create a custom 'Scope'
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
|
// Add variables to the scope
|
||||||
|
scope.push("my_var", 42_i64);
|
||||||
|
scope.push("my_string", "hello, world!");
|
||||||
|
scope.push_constant("MY_CONST", true);
|
||||||
|
|
||||||
|
// Call a function defined in the script
|
||||||
|
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ("abc", 123_i64))?;
|
||||||
|
|
||||||
|
// For a function with one parameter, use a tuple with a trailing comma
|
||||||
|
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", (123_i64,))?;
|
||||||
|
|
||||||
|
// For a function with no parameters
|
||||||
|
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ())?;
|
||||||
|
```
|
187
herodb/aiprompts/rhaiwrapping_best_practices.md
Normal file
187
herodb/aiprompts/rhaiwrapping_best_practices.md
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
## Best Practices and Optimization
|
||||||
|
|
||||||
|
When wrapping Rust functions for use with Rhai, following these best practices will help you create efficient, maintainable, and robust code.
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
|
||||||
|
1. **Minimize Cloning**: Rhai often requires cloning data, but you can minimize this overhead:
|
||||||
|
```rust
|
||||||
|
// Prefer immutable references when possible
|
||||||
|
fn process_data(data: &MyStruct) -> i64 {
|
||||||
|
// Work with data without cloning
|
||||||
|
data.value * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use mutable references for in-place modifications
|
||||||
|
fn update_data(data: &mut MyStruct) {
|
||||||
|
data.value += 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Avoid Excessive Type Conversions**: Converting between Rhai's Dynamic type and Rust types has overhead:
|
||||||
|
```rust
|
||||||
|
// Inefficient - multiple conversions
|
||||||
|
fn process_inefficient(ctx: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
let value = args[0].as_int()?;
|
||||||
|
let result = value * 2;
|
||||||
|
Ok(Dynamic::from(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
// More efficient - use typed parameters when possible
|
||||||
|
fn process_efficient(value: i64) -> i64 {
|
||||||
|
value * 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Batch Operations**: For operations on collections, batch processing is more efficient:
|
||||||
|
```rust
|
||||||
|
// Process an entire array at once rather than element by element
|
||||||
|
fn sum_array(arr: Array) -> Result<i64, Box<EvalAltResult>> {
|
||||||
|
arr.iter()
|
||||||
|
.map(|v| v.as_int())
|
||||||
|
.collect::<Result<Vec<i64>, _>>()
|
||||||
|
.map(|nums| nums.iter().sum())
|
||||||
|
.map_err(|_| "Array must contain only integers".into())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Compile Scripts Once**: Reuse compiled ASTs for scripts that are executed multiple times:
|
||||||
|
```rust
|
||||||
|
// Compile once
|
||||||
|
let ast = engine.compile(script)?;
|
||||||
|
|
||||||
|
// Execute multiple times with different parameters
|
||||||
|
for i in 0..10 {
|
||||||
|
let result = engine.eval_ast::<i64>(&ast)?;
|
||||||
|
println!("Result {}: {}", i, result);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Thread Safety
|
||||||
|
|
||||||
|
1. **Use Sync Mode When Needed**: If you need thread safety, use the `sync` feature:
|
||||||
|
```rust
|
||||||
|
// In Cargo.toml
|
||||||
|
// rhai = { version = "1.x", features = ["sync"] }
|
||||||
|
|
||||||
|
// This creates a thread-safe engine
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
// Now you can safely share the engine between threads
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let result = engine.eval::<i64>("40 + 2")?;
|
||||||
|
println!("Result: {}", result);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Clone the Engine for Multiple Threads**: When not using `sync`, clone the engine for each thread:
|
||||||
|
```rust
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
let handles: Vec<_> = (0..5).map(|i| {
|
||||||
|
let engine_clone = engine.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let result = engine_clone.eval::<i64>(&format!("{} + 2", i * 10))?;
|
||||||
|
println!("Thread {}: {}", i, result);
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
for handle in handles {
|
||||||
|
handle.join().unwrap();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Management
|
||||||
|
|
||||||
|
1. **Control Scope Size**: Be mindful of the size of your scopes:
|
||||||
|
```rust
|
||||||
|
// Create a new scope for each operation to avoid memory buildup
|
||||||
|
for item in items {
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
scope.push("item", item);
|
||||||
|
engine.eval_with_scope::<()>(&mut scope, "process(item)")?;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Limit Script Complexity**: Use engine options to limit script complexity:
|
||||||
|
```rust
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
// Set limits to prevent scripts from consuming too many resources
|
||||||
|
engine.set_max_expr_depths(64, 64) // Max expression/statement depth
|
||||||
|
.set_max_function_expr_depth(64) // Max function depth
|
||||||
|
.set_max_array_size(10000) // Max array size
|
||||||
|
.set_max_map_size(10000) // Max map size
|
||||||
|
.set_max_string_size(10000) // Max string size
|
||||||
|
.set_max_call_levels(64); // Max call stack depth
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Use Shared Values Carefully**: Shared values (via closures) have reference-counting overhead:
|
||||||
|
```rust
|
||||||
|
// Avoid unnecessary capturing in closures when possible
|
||||||
|
engine.register_fn("process", |x: i64| x * 2);
|
||||||
|
|
||||||
|
// Instead of capturing large data structures
|
||||||
|
let large_data = vec![1, 2, 3, /* ... thousands of items ... */];
|
||||||
|
engine.register_fn("process_data", move |idx: i64| {
|
||||||
|
if idx >= 0 && (idx as usize) < large_data.len() {
|
||||||
|
large_data[idx as usize]
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Consider registering a lookup function instead
|
||||||
|
let large_data = std::sync::Arc::new(vec![1, 2, 3, /* ... thousands of items ... */]);
|
||||||
|
let data_ref = large_data.clone();
|
||||||
|
engine.register_fn("lookup", move |idx: i64| {
|
||||||
|
if idx >= 0 && (idx as usize) < data_ref.len() {
|
||||||
|
data_ref[idx as usize]
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Design
|
||||||
|
|
||||||
|
1. **Consistent Naming**: Use consistent naming conventions:
|
||||||
|
```rust
|
||||||
|
// Good: Consistent naming pattern
|
||||||
|
engine.register_fn("create_user", create_user)
|
||||||
|
.register_fn("update_user", update_user)
|
||||||
|
.register_fn("delete_user", delete_user);
|
||||||
|
|
||||||
|
// Bad: Inconsistent naming
|
||||||
|
engine.register_fn("create_user", create_user)
|
||||||
|
.register_fn("user_update", update_user)
|
||||||
|
.register_fn("remove", delete_user);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Logical Function Grouping**: Group related functions together:
|
||||||
|
```rust
|
||||||
|
// Register all string-related functions together
|
||||||
|
engine.register_fn("str_length", |s: &str| s.len() as i64)
|
||||||
|
.register_fn("str_uppercase", |s: &str| s.to_uppercase())
|
||||||
|
.register_fn("str_lowercase", |s: &str| s.to_lowercase());
|
||||||
|
|
||||||
|
// Register all math-related functions together
|
||||||
|
engine.register_fn("math_sin", |x: f64| x.sin())
|
||||||
|
.register_fn("math_cos", |x: f64| x.cos())
|
||||||
|
.register_fn("math_tan", |x: f64| x.tan());
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Comprehensive Documentation**: Document your API thoroughly:
|
||||||
|
```rust
|
||||||
|
// Add documentation for script writers
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
{
|
||||||
|
// Add function documentation
|
||||||
|
engine.register_fn("calculate_tax", calculate_tax)
|
||||||
|
.register_fn_metadata("calculate_tax", |metadata| {
|
||||||
|
metadata.set_doc_comment("Calculates tax based on income and rate.\n\nParameters:\n- income: Annual income\n- rate: Tax rate (0.0-1.0)\n\nReturns: Calculated tax amount");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user