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

64
Cargo.lock generated
View File

@@ -24,15 +24,15 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitflags"
version = "2.9.1"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]]
name = "cfg-if"
version = "1.0.1"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "const-random"
@@ -80,7 +80,7 @@ dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
"wasi 0.14.7+wasi-0.2.4",
]
[[package]]
@@ -154,9 +154,9 @@ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "proc-macro2"
version = "1.0.97"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
@@ -240,9 +240,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rhai"
version = "1.22.2"
version = "1.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249"
checksum = "527390cc333a8d2cd8237890e15c36518c26f8b54c903d86fc59f42f08d25594"
dependencies = [
"ahash",
"bitflags",
@@ -258,7 +258,7 @@ dependencies = [
[[package]]
name = "rhai-python"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"pyo3",
"pyo3-build-config",
@@ -267,9 +267,9 @@ dependencies = [
[[package]]
name = "rhai_codegen"
version = "2.2.0"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
checksum = "d4322a2a4e8cf30771dd9f27f7f37ca9ac8fe812dddd811096a98483080dabe6"
dependencies = [
"proc-macro2",
"quote",
@@ -307,9 +307,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "2.0.104"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
@@ -318,9 +318,9 @@ dependencies = [
[[package]]
name = "target-lexicon"
version = "0.13.2"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
[[package]]
name = "thin-vec"
@@ -339,9 +339,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.18"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "unindent"
@@ -363,36 +363,42 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wit-bindgen-rt",
"wasip2",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"bitflags",
"wit-bindgen",
]
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.26"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.26"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -1,6 +1,6 @@
[package]
name = "rhai-python"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
[lib]

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(())
}