From a361f6d4baf99b9367aa5c0fd2adc55d6ddefc31 Mon Sep 17 00:00:00 2001 From: kristof Date: Thu, 3 Apr 2025 13:12:08 +0200 Subject: [PATCH] ... --- .../loadscripts/test_dynamic_loading.rs | 83 +++----------- rhai_engine/src/rhailoader/script_manager.rs | 107 +++++++++++++----- 2 files changed, 99 insertions(+), 91 deletions(-) diff --git a/rhai_engine/examples/loadscripts/test_dynamic_loading.rs b/rhai_engine/examples/loadscripts/test_dynamic_loading.rs index b0e3378..25c62fe 100644 --- a/rhai_engine/examples/loadscripts/test_dynamic_loading.rs +++ b/rhai_engine/examples/loadscripts/test_dynamic_loading.rs @@ -42,32 +42,26 @@ pub fn main() -> Result<(), String> { // Test some functions from each script println!("\nDynamic function testing:"); - // Test string_utils functions - test_function(&script_manager, "string_utils:capitalize", vec![Dynamic::from("hello world")])?; - - // Test math_utils functions - test_function(&script_manager, "math_utils:format_number", vec![Dynamic::from(1234567)])?; - - // Test test_utils functions - test_function(&script_manager, "test_utils:reverse_string", vec![Dynamic::from("hello world")])?; - - // Using direct script execution - println!("\nExecuting scripts directly:"); - - // Direct function call with arguments as a script - execute_script(&script_manager, "test_utils:count_words(\"this is a test sentence\")")?; - - // Direct script with expression - execute_script(&script_manager, "test_utils:factorial(5)")?; - - // Run a more complex script + //TODO: should not start with string_utils we want capitalize as function usable as is + script_manager.run( "string_utils:capitalize(hello world);")?; + script_manager.run( "math_utils:format_number(1234567);")?; + + + // Test evaluating a complex script + println!("\nEvaluating a complex script:"); let complex_script = r#" - let result = test_utils:count_words("Count the words in this sentence"); - print("Word count: " + result); - result * 2 // Return double the word count + let sentence = "Count the words in this sentence"; + let count = test_utils:count_words(sentence); + print("Word count: " + truncate("count",1)); + count * 2 // Return double the word count "#; - - execute_script_with_result(&script_manager, complex_script)?; + + // Evaluate the script + println!("```\n{}\n```", complex_script); + match script_manager.eval::(complex_script) { + Ok(result) => println!(" Result: {}", result), + Err(e) => println!(" Error: {}", e) + } println!("\n=== DYNAMIC FUNCTION LOADING TEST COMPLETE ===\n"); @@ -102,44 +96,3 @@ fn display_functions_by_script(scripts: &HashMap>) { } } } - -// Helper function to test a function call and display its result -fn test_function(script_manager: &ScriptManager, name: &str, args: Vec) -> Result<(), String> { - // Call the function with the provided arguments - let result = script_manager.call_function(name, args)?; - - // Print the result - println!(" {}() => {}", name, result); - - Ok(()) -} - -// Execute a script directly and display the result -fn execute_script(script_manager: &ScriptManager, script: &str) -> Result<(), String> { - println!(" Executing: {}", script); - - // Use the run method to execute the script - script_manager.run(script)?; - - // For functions that return a value, we can use eval to get the result - if let Ok(result) = script_manager.eval::(script) { - println!(" Result: {}", result); - } - - Ok(()) -} - -// Execute a script and return its result -fn execute_script_with_result(script_manager: &ScriptManager, script: &str) -> Result<(), String> { - println!(" Executing script with result:"); - println!("```"); - println!("{}", script); - println!("```"); - - // Use the eval method to execute the script and get the result - let result = script_manager.eval::(script)?; - - println!(" Result: {}", result); - - Ok(()) -} \ No newline at end of file diff --git a/rhai_engine/src/rhailoader/script_manager.rs b/rhai_engine/src/rhailoader/script_manager.rs index 09bdd8a..a275416 100644 --- a/rhai_engine/src/rhailoader/script_manager.rs +++ b/rhai_engine/src/rhailoader/script_manager.rs @@ -121,7 +121,7 @@ impl ScriptManager { Ok(()) } - /// Extract functions from a script and map them to callable wrappers + /// Extract functions from a script and register them with the engine fn map_script_functions(&mut self, script_name: &str) -> Result<(), String> { let script_arc = self.scripts.get(script_name) .ok_or_else(|| format!("Script not found: {}", script_name))? @@ -141,43 +141,100 @@ impl ScriptManager { let fn_name = fn_def.name.to_string(); println!(" - {} (params: {})", fn_name, fn_def.params.len()); + // Create a fully qualified name for the function (script:function) let full_name = format!("{}:{}", script_name, fn_name); - let script_arc_clone = script_arc.clone(); - let fn_name_owned = fn_name.clone(); - // Create a closure that will call this function using a shared engine configuration + // Create strong references to required data + let script_arc_clone = script_arc.clone(); + let fn_name_clone = fn_name.clone(); + + // Create a function wrapper that will be called from Rust let function_wrapper: RhaiFn = Box::new(move |args: Vec| -> Result { - // Create a configured engine for each invocation - let engine = create_standard_engine(); + // Important: We need to create a scope for each call let mut scope = Scope::new(); - // Call the function + // Use a clone of self.engine - this ensures we're using the same engine + // instance while avoiding ownership issues in the closure + let mut engine = Engine::new(); + + // Configure the engine with the same settings + // This is a workaround for the ownership issue with closures + engine.set_fast_operators(true); + + // Register the same built-in string functions + engine.register_fn("substr", |s: &str, start: i64, len: i64| { + let start = start as usize; + let len = len as usize; + if start >= s.len() { return String::new(); } + let end = (start + len).min(s.len()); + s[start..end].to_string() + }); + engine.register_fn("to_upper", |s: &str| s.to_uppercase()); + engine.register_fn("to_lower", |s: &str| s.to_lowercase()); + engine.register_fn("split", |text: &str, delimiter: &str| -> Array { + text.split(delimiter).map(|s| Dynamic::from(s.to_string())).collect() + }); + engine.register_fn("split", |s: &str| -> Array { + s.split_whitespace() + .map(|part| Dynamic::from(part.to_string())) + .collect() + }); + engine.register_fn("len", |array: &mut Array| -> i64 { + array.len() as i64 + }); + engine.register_fn("join", |arr: &mut Array, separator: &str| -> String { + arr.iter() + .map(|v| v.to_string()) + .collect::>() + .join(separator) + }); + engine.register_fn("replace", |s: &str, from: &str, to: &str| -> String { + s.replace(from, to) + }); + engine.register_fn("push", |arr: &mut Array, value: Dynamic| { + arr.push(value); + Dynamic::from(()) + }); + engine.register_fn("len", |s: &str| -> i64 { + s.len() as i64 + }); + engine.register_fn("..", |start: i64, end: i64| { + let mut arr = Array::new(); + for i in start..end { arr.push(Dynamic::from(i)); } + arr + }); + + // Call the function using the configured engine engine.call_fn::( - &mut scope, - &script_arc_clone.read().unwrap(), - &fn_name_owned, + &mut scope, + &script_arc_clone.read().unwrap(), + &fn_name_clone, args - ).map_err(|e| format!("Error calling function {}: {}", fn_name_owned, e)) + ).map_err(|e| format!("Error calling function {}: {}", fn_name_clone, e)) }); - // Store the function wrapper in the maps + // Store the function wrapper in the functions map self.functions.insert(full_name, function_wrapper); - // Create a new wrapper for the filter + // Also create a filter wrapper with the same functionality but specific to filters let script_arc_clone = script_arc.clone(); - let fn_name_owned = fn_name.clone(); + let fn_name_clone = fn_name.clone(); let filter_wrapper: RhaiFn = Box::new(move |args: Vec| -> Result { - // Use the same engine creation function - let engine = create_standard_engine(); + // Create a scope and engine similar to the function wrapper let mut scope = Scope::new(); + let mut engine = Engine::new(); + // Configure the engine with the same settings + engine.set_fast_operators(true); + + // Call the function (no need to register all functions again as filters don't use them) engine.call_fn::( - &mut scope, - &script_arc_clone.read().unwrap(), - &fn_name_owned, + &mut scope, + &script_arc_clone.read().unwrap(), + &fn_name_clone, args - ).map_err(|e| format!("Error calling filter {}: {}", fn_name_owned, e)) + ).map_err(|e| format!("Error calling filter {}: {}", fn_name_clone, e)) }); // Store the filter wrapper @@ -294,23 +351,21 @@ impl ScriptManager { /// Execute a script snippet directly pub fn eval(&self, script: &str) -> Result where T: 'static + Clone { - // Create a configured engine for execution - let engine = create_standard_engine(); + // Use the existing engine and create a new scope let mut scope = Scope::new(); // Execute the script and return the result - engine.eval_with_scope::(&mut scope, script) + self.engine.eval_with_scope::(&mut scope, script) .map_err(|e| format!("Error evaluating script: {}", e)) } /// Run a script without returning a value pub fn run(&self, script: &str) -> Result<(), String> { - // Create a configured engine for execution - let engine = create_standard_engine(); + // Use the existing engine instance let mut scope = Scope::new(); // Run the script - engine.run_with_scope(&mut scope, script) + self.engine.run_with_scope(&mut scope, script) .map_err(|e| format!("Error running script: {}", e)) } } \ No newline at end of file