From 05a4b466d1f8a7f8cf4615f01f24b2d99f9a9c8f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 6 Jul 2020 12:06:57 +0800 Subject: [PATCH] New register_raw_fn_n shortcuts. --- doc/src/rust/register-raw.md | 127 ++++++++++++++++++++++++++ src/api.rs | 171 ++++++++++++++++++++++++++++++++++- 2 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 doc/src/rust/register-raw.md diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md new file mode 100644 index 00000000..429dae4a --- /dev/null +++ b/doc/src/rust/register-raw.md @@ -0,0 +1,127 @@ +Use the Low-Level API to Register a Rust Function +================================================ + +{{#include ../links.md}} + +When a native Rust function is registered with an `Engine` using the `Engine::register_XXX` API, +Rhai transparently converts all function arguments from [`Dynamic`] into the correct types before +calling the function. + +For more power and flexibility, there is a _low-level_ API to work directly with [`Dynamic`] values +without the conversions. + + +Raw Function Registration +------------------------- + +The `Engine::register_raw_fn` method is marked _volatile_, meaning that it may be changed without warning. + +If this is acceptable, then using this method to register a Rust function opens up more opportunities. + +In particular, a reference to the current `Engine` instance is passed as an argument so the Rust function +can also use `Engine` facilities (like evaluating a script). + +```rust +engine.register_raw_fn( + "increment_by", // function name + &[ // a slice containing parameter types + std::any::TypeId::of::(), // type of first parameter + std::any::TypeId::of::() // type of second parameter + ], + |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { // fixed function signature + // Arguments are guaranteed to be correct in number and of the correct types. + + // But remember this is Rust, so you can keep only one mutable reference at any one time! + // Therefore, get a '&mut' reference to the first argument _last_. + // Alternatively, use `args.split_at_mut(1)` etc. to split the slice first. + + let y: i64 = *args[1].downcast_ref::() // get a reference to the second argument + .unwrap(); // then copying it because it is a primary type + + let y: i64 = std::mem::take(args[1]).cast::(); // alternatively, directly 'consume' it + + let x: &mut i64 = args[0].downcast_mut::() // get a '&mut' reference to the + .unwrap(); // first argument + + *x += y; // perform the action + + Ok(().into()) // must be 'Result>' + } +); + +// The above is the same as (in fact, internally they are equivalent): + +engine.register_fn("increment_by", |x: &mut i64, y: i64| x += y); +``` + + +Shortcuts +--------- + +As usual with Rhai, there are shortcuts. For functions of zero to four parameters, which should be +the majority, use one of the `Engine::register_raw_fn_n` (where `n = 0..4`) methods: + +```rust +// Specify parameter types as generics +engine.register_raw_fn_2::( + "increment_by", + |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { ... } +); +``` + + +Closure Signature +----------------- + +The closure passed to `Engine::register_raw_fn` takes the following form: + +`Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result> + 'static` + +where: + +* `engine` - a reference to the current [`Engine`], with all configurations and settings. + +* `lib` - a reference to the current collection of script-defined functions, as a [`Module`]. + +* `args` - a reference to a slice containing `&mut` references to [`Dynamic`] values. + The slice is guaranteed to contain enough arguments _of the correct types_. + +Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned). +Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations +will be on the cloned copy. + + +Extract Arguments +----------------- + +To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: + +| Argument type | Access (`n` = argument position) | Result | +| ------------------------------ | -------------------------------------- | ---------------------------------------------------------- | +| [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | +| Custom type | `args[n].downcast_ref::().unwrap()` | Immutable reference to value. | +| Custom type (consumed) | `mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | +| `this` object | `args[0].downcast_mut::().unwrap()` | Mutable reference to value. | + +When there is a mutable reference to the `this` object (i.e. the first argument), +there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. + + +Hold Multiple References +------------------------ + +In order to access a value argument that is expensive to clone _while_ holding a mutable reference +to the first argument, either _consume_ that argument via `mem::take` as above, or use `args.split_at` +to partition the slice: + +```rust +// Partition the slice +let (first, rest) = args.split_at_mut(1); + +// Mutable reference to the first parameter +let this_ptr = first[0].downcast_mut::().unwrap(); + +// Immutable reference to the second value parameter +// This can be mutable but there is no point because the parameter is passed by value +let value = rest[0].downcast_ref::().unwrap(); +``` diff --git a/src/api.rs b/src/api.rs index 343caaed..c17f7afb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -33,7 +33,7 @@ use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; /// Engine public API impl Engine { - /// Register a function with the `Engine`. + /// Register a function of the `Engine`. /// /// ## WARNING - Low Level API /// @@ -52,7 +52,7 @@ impl Engine { pub fn register_raw_fn( &mut self, name: &str, - args: &[TypeId], + arg_types: &[TypeId], #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + 'static, @@ -62,7 +62,172 @@ impl Engine { + Sync + 'static, ) { - self.global_module.set_fn_var_args(name, args, func); + self.global_module.set_fn_var_args(name, arg_types, func); + } + + /// Register a function of no parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_0( + &mut self, + name: &str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module.set_fn_var_args(name, &[], func); + } + + /// Register a function of one parameter with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`. + /// The argument is guaranteed to be of the correct type. + /// + /// To get access to a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To get access to the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_1( + &mut self, + name: &str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module + .set_fn_var_args(name, &[TypeId::of::()], func); + } + + /// Register a function of two parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`. + /// The arguments are guaranteed to be of the correct types. + /// + /// To get access to a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To get access to the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_2( + &mut self, + name: &str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module + .set_fn_var_args(name, &[TypeId::of::(), TypeId::of::()], func); + } + + /// Register a function of three parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`. + /// The arguments are guaranteed to be of the correct types. + /// + /// To get access to a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To get access to the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_3( + &mut self, + name: &str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module.set_fn_var_args( + name, + &[TypeId::of::(), TypeId::of::(), TypeId::of::()], + func, + ); + } + + /// Register a function of four parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`. + /// The arguments are guaranteed to be of the correct types. + /// + /// To get access to a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To get access to the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_4< + A: Variant + Clone, + B: Variant + Clone, + C: Variant + Clone, + D: Variant + Clone, + >( + &mut self, + name: &str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module.set_fn_var_args( + name, + &[ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ], + func, + ); } /// Register a custom type for use with the `Engine`.