..
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