allow calling simple Python functions from Rhai

This commit is contained in:
2025-09-19 14:52:31 +02:00
parent 81852334d5
commit d15c8b3e29
3 changed files with 206 additions and 30 deletions

View File

@@ -81,6 +81,7 @@ fn python_to_dynamic(py: Python<'_>, obj: &Bound<'_, PyAny>) -> PyResult<Dynamic
struct RhaiEngine {
engine: Engine,
scope: Scope<'static>,
py_functions: std::collections::HashMap<String, PyObject>,
}
#[pymethods]
@@ -90,6 +91,7 @@ impl RhaiEngine {
Self {
engine: Engine::new(),
scope: Scope::new(),
py_functions: std::collections::HashMap::new(),
}
}
@@ -150,11 +152,179 @@ impl RhaiEngine {
.map(|(name, _, _)| name.to_string())
.collect()
}
/// Register a Python function to be callable from Rhai using signature inspection
fn register_fn(&mut self, py: Python<'_>, name: &str, func: PyObject) -> PyResult<()> {
// Get the function signature using inspect.signature()
let inspect_module = py.import("inspect")?;
let signature_func = inspect_module.getattr("signature")?;
let sig = signature_func.call1((&func,))?;
let parameters = sig.getattr("parameters")?;
let param_count = parameters.len()?;
// Store the function
self.py_functions
.insert(name.to_string(), func.clone_ref(py));
let func_name = name.to_string();
// Register the function with the appropriate arity
match param_count {
0 => {
self.engine.register_fn(name, {
let func = func.clone_ref(py);
let name = func_name.clone();
move || -> Result<Dynamic, Box<rhai::EvalAltResult>> {
Python::with_gil(|py| {
let result = func
.call0(py)
.map_err(|e| format!("Python function '{}' error: {}", name, e))?;
python_to_dynamic(py, &result.bind(py))
.map_err(|e| format!("Type conversion error: {}", e).into())
})
}
});
}
1 => {
self.engine.register_fn(name, {
let func = func.clone_ref(py);
let name = func_name.clone();
move |arg1: Dynamic| -> Result<Dynamic, Box<rhai::EvalAltResult>> {
Python::with_gil(|py| {
let py_arg1 = dynamic_to_python(py, arg1)
.map_err(|e| format!("Type conversion error: {}", e))?;
let result = func
.call1(py, (py_arg1,))
.map_err(|e| format!("Python function '{}' error: {}", name, e))?;
python_to_dynamic(py, &result.bind(py))
.map_err(|e| format!("Type conversion error: {}", e).into())
})
}
});
}
2 => {
self.engine.register_fn(name, {
let func = func.clone_ref(py);
let name = func_name.clone();
move |arg1: Dynamic,
arg2: Dynamic|
-> Result<Dynamic, Box<rhai::EvalAltResult>> {
Python::with_gil(|py| {
let py_arg1 = dynamic_to_python(py, arg1)
.map_err(|e| format!("Type conversion error: {}", e))?;
let py_arg2 = dynamic_to_python(py, arg2)
.map_err(|e| format!("Type conversion error: {}", e))?;
let result = func
.call1(py, (py_arg1, py_arg2))
.map_err(|e| format!("Python function '{}' error: {}", name, e))?;
python_to_dynamic(py, &result.bind(py))
.map_err(|e| format!("Type conversion error: {}", e).into())
})
}
});
}
3 => {
self.engine.register_fn(name, {
let func = func.clone_ref(py);
let name = func_name.clone();
move |arg1: Dynamic,
arg2: Dynamic,
arg3: Dynamic|
-> Result<Dynamic, Box<rhai::EvalAltResult>> {
Python::with_gil(|py| {
let py_arg1 = dynamic_to_python(py, arg1)
.map_err(|e| format!("Type conversion error: {}", e))?;
let py_arg2 = dynamic_to_python(py, arg2)
.map_err(|e| format!("Type conversion error: {}", e))?;
let py_arg3 = dynamic_to_python(py, arg3)
.map_err(|e| format!("Type conversion error: {}", e))?;
let result = func
.call1(py, (py_arg1, py_arg2, py_arg3))
.map_err(|e| format!("Python function '{}' error: {}", name, e))?;
python_to_dynamic(py, &result.bind(py))
.map_err(|e| format!("Type conversion error: {}", e).into())
})
}
});
}
4 => {
self.engine.register_fn(name, {
let func = func.clone_ref(py);
let name = func_name.clone();
move |arg1: Dynamic,
arg2: Dynamic,
arg3: Dynamic,
arg4: Dynamic|
-> Result<Dynamic, Box<rhai::EvalAltResult>> {
Python::with_gil(|py| {
let py_arg1 = dynamic_to_python(py, arg1)
.map_err(|e| format!("Type conversion error: {}", e))?;
let py_arg2 = dynamic_to_python(py, arg2)
.map_err(|e| format!("Type conversion error: {}", e))?;
let py_arg3 = dynamic_to_python(py, arg3)
.map_err(|e| format!("Type conversion error: {}", e))?;
let py_arg4 = dynamic_to_python(py, arg4)
.map_err(|e| format!("Type conversion error: {}", e))?;
let result = func
.call1(py, (py_arg1, py_arg2, py_arg3, py_arg4))
.map_err(|e| format!("Python function '{}' error: {}", name, e))?;
python_to_dynamic(py, &result.bind(py))
.map_err(|e| format!("Type conversion error: {}", e).into())
})
}
});
}
5 => {
self.engine.register_fn(name, {
let func = func.clone_ref(py);
let name = func_name.clone();
move |arg1: Dynamic,
arg2: Dynamic,
arg3: Dynamic,
arg4: Dynamic,
arg5: Dynamic|
-> Result<Dynamic, Box<rhai::EvalAltResult>> {
Python::with_gil(|py| {
let py_arg1 = dynamic_to_python(py, arg1)
.map_err(|e| format!("Type conversion error: {}", e))?;
let py_arg2 = dynamic_to_python(py, arg2)
.map_err(|e| format!("Type conversion error: {}", e))?;
let py_arg3 = dynamic_to_python(py, arg3)
.map_err(|e| format!("Type conversion error: {}", e))?;
let py_arg4 = dynamic_to_python(py, arg4)
.map_err(|e| format!("Type conversion error: {}", e))?;
let py_arg5 = dynamic_to_python(py, arg5)
.map_err(|e| format!("Type conversion error: {}", e))?;
let result = func
.call1(py, (py_arg1, py_arg2, py_arg3, py_arg4, py_arg5))
.map_err(|e| format!("Python function '{}' error: {}", name, e))?;
python_to_dynamic(py, &result.bind(py))
.map_err(|e| format!("Type conversion error: {}", e).into())
})
}
});
}
_ => {
return Err(PyRuntimeError::new_err(format!(
"Function '{}' has {} parameters, but only 0-5 parameters are supported",
name, param_count
)));
}
}
Ok(())
}
/// List registered Python functions
fn list_python_functions(&self) -> Vec<String> {
self.py_functions.keys().cloned().collect()
}
}
/// A Python module implemented in Rust using PyO3
#[pymodule]
fn rhai_python(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<RhaiEngine>()?;
m.add("__version__", "0.1.1")?;
Ok(())
}