..
This commit is contained in:
		
										
											
												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"); | ||||
|              }); | ||||
|    } | ||||
|    ``` | ||||
		Reference in New Issue
	
	Block a user