Encapsulate register_fn_raw parameters into NativeCallContext.

This commit is contained in:
Stephen Chung
2020-10-18 17:02:17 +08:00
parent 58c820785b
commit 82e6dd446a
20 changed files with 368 additions and 265 deletions

View File

@@ -17,10 +17,11 @@ Built-in methods
The following standard methods (mostly defined in the [`BasicFnPackage`][packages] but excluded if
using a [raw `Engine`]) operate on function pointers:
| Function | Parameter(s) | Description |
| -------------------------- | ------------ | ---------------------------------------------------------------------------- |
| `name` method and property | _none_ | returns the name of the function encapsulated by the function pointer |
| `call` | _arguments_ | calls the function matching the function pointer's name with the _arguments_ |
| Function | Parameter(s) | Description |
| ---------------------------------- | ------------ | ---------------------------------------------------------------------------- |
| `name` method and property | _none_ | returns the name of the function encapsulated by the function pointer |
| `is_anonymous` method and property | _none_ | does the function pointer refer to an [anonymous function]? |
| `call` | _arguments_ | calls the function matching the function pointer's name with the _arguments_ |
Examples
@@ -186,16 +187,15 @@ must be used to register the function.
Essentially, use the low-level `Engine::register_raw_fn` method to register the function.
`FnPtr::call_dynamic` is used to actually call the function pointer, passing to it the
current scripting [`Engine`], collection of script-defined functions, the `this` pointer,
and other necessary arguments.
current _native call context_, the `this` pointer, and other necessary arguments.
```rust
use rhai::{Engine, Module, Dynamic, FnPtr};
use rhai::{Engine, Module, Dynamic, FnPtr, NativeCallContext};
let mut engine = Engine::new();
// Define Rust function in required low-level API signature
fn call_fn_ptr_with_value(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic])
fn call_fn_ptr_with_value(context: NativeCallContext, args: &mut [&mut Dynamic])
-> Result<Dynamic, Box<EvalAltResult>>
{
// 'args' is guaranteed to contain enough arguments of the correct types
@@ -205,7 +205,7 @@ fn call_fn_ptr_with_value(engine: &Engine, lib: &Module, args: &mut [&mut Dynami
// Use 'FnPtr::call_dynamic' to call the function pointer.
// Beware, private script-defined functions will not be found.
fp.call_dynamic(engine, lib, Some(this_ptr), [value])
fp.call_dynamic(context, Some(this_ptr), [value])
}
// Register a Rust function using the low-level API
@@ -218,3 +218,43 @@ engine.register_raw_fn("super_call",
call_fn_ptr_with_value
);
```
`NativeCallContext`
------------------
`FnPtr::call_dynamic` takes a parameter of type `NativeCallContext` which holds the _native call context_
of the particular call to a registered Rust function.
This type is normally provided by the [`Engine`] (e.g. when using `Engine::register_fn_raw`(../rust/register-raw.md)).
However, it may also be manually constructed from a tuple:
```rust
use rhai::{Engine, FnPtr};
let engine = Engine::new();
// Compile script to AST
let mut ast = engine.compile(
r#"
let test = "hello";
|x| test + x // this creates an closure
"#,
)?;
// Save the closure together with captured variables
let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;
// Get rid of the script, retaining only functions
ast.retain_functions(|_, _, _| true);
// Create native call context via a tuple containing the Engine and the
// set of script-defined functions (within the AST)
let context = (&engine, ast.as_ref()).into();
// 'f' captures: the engine, the AST, and the closure
let f = move |x: i64| fn_ptr.call_dynamic(context, None, [x.into()]);
// 'f' can be called like a normal function
let result = f(42)?;
```

View File

@@ -18,8 +18,9 @@ The `Engine::register_raw_fn` method is marked _volatile_, meaning that it may b
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).
In particular, a the current _native call context_ (in form of the `NativeCallContext` type) is passed as an argument.
`NativeCallContext` exposes the current [`Engine`], among others, so the Rust function can also use [`Engine`] facilities
(such as evaluating a script).
```rust
engine.register_raw_fn(
@@ -28,7 +29,7 @@ engine.register_raw_fn(
std::any::TypeId::of::<i64>(), // type of first parameter
std::any::TypeId::of::<i64>() // type of second parameter
],
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { // fixed function signature
|context, args| { // 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!
@@ -59,17 +60,19 @@ Function Signature
The function signature passed to `Engine::register_raw_fn` takes the following form:
> `Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic])`
> `-> Result<T, Box<EvalAltResult>> + 'static`
> `Fn(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> + 'static`
where:
* `T: Variant + Clone` - return type of the function.
* `engine: &Engine` - the current [`Engine`], with all configurations and settings.
* `context: NativeCallContext` - the current _native call context_, which exposes the following:
* `lib: &Module` - the current global library of script-defined functions, as a [`Module`].
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`].
* `context.engine(): &Engine` - the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context
using [`Engine::call_fn`][`call_fn`].
* `context.namespace(): &Module` - the global namespace of script-defined functions, as a [`Module`].
* `args: &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values.
The slice is guaranteed to contain enough arguments _of the correct types_.
@@ -106,7 +109,7 @@ then calls it within the same [`Engine`]. This way, a _callback_ function can b
to a native Rust function.
```rust
use rhai::{Engine, Module, Dynamic, FnPtr};
use rhai::{Engine, FnPtr};
let mut engine = Engine::new();
@@ -118,7 +121,7 @@ engine.register_raw_fn(
std::any::TypeId::of::<FnPtr>(),
std::any::TypeId::of::<i64>(),
],
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|context, args| {
// 'args' is guaranteed to contain enough arguments of the correct types
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer
@@ -127,7 +130,7 @@ engine.register_raw_fn(
// Use 'FnPtr::call_dynamic' to call the function pointer.
// Beware, private script-defined functions will not be found.
fp.call_dynamic(engine, lib, Some(this_ptr), [value])
fp.call_dynamic(context, Some(this_ptr), [value])
},
);