Expand packages and raw Engine write-up.

This commit is contained in:
Stephen Chung 2020-08-23 17:22:39 +08:00
parent 196e145c96
commit e2f271644a
10 changed files with 127 additions and 12 deletions

View File

@ -23,7 +23,7 @@ The Rhai Scripting Language
1. [Hello World in Rhai - Evaluate a Script](engine/hello-world.md)
2. [Compile to AST for Repeated Evaluations](engine/compile.md)
3. [Call a Rhai Function from Rust](engine/call-fn.md)
4. [Create a Rust Anonymous Function from a Rhai Function](engine/func.md)
4. [Create a Rust Closure from a Rhai Function](engine/func.md)
5. [Evaluate Expressions Only](engine/expressions.md)
6. [Raw Engine](engine/raw.md)
4. [Extend Rhai with Rust](rust/index.md)
@ -106,6 +106,7 @@ The Rhai Scripting Language
2. [Loadable Configuration](patterns/config.md)
3. [Control Layer](patterns/control.md)
4. [Singleton Command](patterns/singleton.md)
5. [One Engine Instance Per Call](patterns/parallel.md)
2. [Capture Scope for Function Call](language/fn-capture.md)
3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md)
4. [Script Optimization](engine/optimize/index.md)

View File

@ -1,11 +1,11 @@
Create a Rust Anonymous Function from a Rhai Function
===================================================
Create a Rust Closure from a Rhai Function
=========================================
{{#include ../links.md}}
It is possible to further encapsulate a script in Rust such that it becomes a normal Rust function.
Such an _anonymous function_ is basically a boxed closure, very useful as call-back functions.
Such a _closure_ is very useful as call-back functions.
Creating them is accomplished via the `Func` trait which contains `create_from_script`
(as well as its companion method `create_from_ast`):
@ -30,7 +30,7 @@ let func = Func::<(i64, String), bool>::create_from_script(
"calc" // the entry-point function name
)?;
func(123, "hello".to_string())? == false; // call the anonymous function
func(123, "hello".to_string())? == false; // call the closure
schedule_callback(func); // pass it as a callback to another function

View File

@ -35,7 +35,8 @@ The actual implementation de-sugars to:
1. Keeping track of what variables are accessed inside the anonymous function,
2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and in the current execution scope - where the anonymous function is created.
2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and
in the current execution scope - where the anonymous function is created.
3. The variable is added to the parameters list of the anonymous function, at the front.
@ -43,7 +44,8 @@ The actual implementation de-sugars to:
An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai.
5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value and inserting it into future calls of the function.
5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value
and inserting it into future calls of the function.
This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures.

View File

@ -31,6 +31,8 @@
[built-in operators]: {{rootUrl}}/engine/raw.md#built-in-operators
[package]: {{rootUrl}}/rust/packages/index.md
[packages]: {{rootUrl}}/rust/packages/index.md
[custom package]: {{rootUrl}}/rust/packages/create.md
[custom packages]: {{rootUrl}}/rust/packages/create.md
[`Scope`]: {{rootUrl}}/rust/scope.md
[`serde`]: {{rootUrl}}/rust/serde.md

View File

@ -0,0 +1,70 @@
One Engine Instance Per Call
===========================
{{#include ../links.md}}
Usage Scenario
--------------
* A system where scripts are called a _lot_, in tight loops or in parallel.
* Keeping a global [`Engine`] instance is sub-optimal due to contention and locking.
* Scripts need to be executed independently from each other, perhaps concurrently.
* Scripts are used to [create Rust closure][`Func`] that are stored and may be called at any time, perhaps concurrently.
In this case, the [`Engine`] instance is usually moved into the closure itself.
Key Concepts
------------
* Create a single instance of each standard [package] required. To duplicate `Engine::new`, create a [`StandardPackage`]({{rootUrl}}/rust/packages/builtin.md).
* Gather up all common custom functions into a [custom package].
* Store a global `AST` for use with all engines.
* Always use `Engine::new_raw` to create a [raw `Engine`], instead of `Engine::new` which is _much_ more expensive.
A [raw `Engine`] is _extremely_ cheap to create.
Loading the [`StandardPackage`]({{rootUrl}}/rust/packages/builtin.md) into a [raw `Engine`] via `Engine::load_package` is essentially the same as `Engine::new`.
But because packages are shared, loading an existing package is _much cheaper_ than registering all the functions one by one.
* Load the required packages into the [raw `Engine`] via `Engine::load_package`, using `Package::get` to obtain a shared copy.
Examples
--------
```rust
use rhai::packages::{Package, StandardPackage};
let ast = /* ... some AST ... */;
let std_pkg = StandardPackage::new();
let custom_pkg = MyCustomPackage::new();
let make_call = |x: i64| -> Result<(), Box<EvalAltResult>> {
// Create a raw Engine - extremely cheap.
let mut engine = Engine::new_raw();
// Load packages - cheap.
engine.load_package(std_pkg.get());
engine.load_package(custom_pkg.get());
// Create custom scope - cheap.
let mut scope = Scope::new();
// Push variable into scope - relatively cheap.
scope.push("x", x);
// Evaluate script.
engine.consume_ast_with_scope(&mut scope, &ast)
};
// The following loop creates 10,000 Engine instances!
for x in 0..10_000 {
make_call(x)?;
}
```

View File

@ -5,6 +5,14 @@ Create a Custom Package
Sometimes specific functionalities are needed, so custom packages can be created.
A custom package is a convenient means to gather up a number of functions for later use.
An [`Engine`] only needs to `Engine::load_package` the custom package once to gain access
to the entire set of functions within.
Loading a package into an [`Engine`] is functionally equivalent to calling `Engine::register_fn` etc.
on _each_ of the functions inside the package. But because packages are _shared_, loading an existing
package is _much_ cheaper than registering all the functions one by one.
The macro `rhai::def_package!` is used to create a new custom package.

View File

@ -9,5 +9,5 @@ A number of traits, under the `rhai::` module namespace, provide additional func
| ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- |
| `RegisterFn` | Trait for registering functions | `register_fn` |
| `RegisterResultFn` | Trait for registering fallible functions returning `Result<Dynamic, Box<EvalAltResult>>` | `register_result_fn` |
| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` |
| `Func` | Trait for creating Rust closures from script | `create_from_ast`, `create_from_script` |
| `ModuleResolver` | Trait implemented by module resolution services | `resolve` |

View File

@ -378,7 +378,6 @@ pub struct Limits {
/// ```
///
/// Currently, `Engine` is neither `Send` nor `Sync`. Use the `sync` feature to make it `Send + Sync`.
#[derive(Clone)]
pub struct Engine {
/// A unique ID identifying this scripting `Engine`.
pub id: Option<String>,

View File

@ -12,11 +12,11 @@ use crate::scope::Scope;
use crate::stdlib::{boxed::Box, string::ToString};
/// Trait to create a Rust anonymous function from a script.
/// Trait to create a Rust closure from a script.
pub trait Func<ARGS, RET> {
type Output;
/// Create a Rust anonymous function from an `AST`.
/// Create a Rust closure from an `AST`.
/// The `Engine` and `AST` are consumed and basically embedded into the closure.
///
/// # Examples
@ -47,7 +47,7 @@ pub trait Func<ARGS, RET> {
/// # }
fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output;
/// Create a Rust anonymous function from a script.
/// Create a Rust closure from a script.
/// The `Engine` is consumed and basically embedded into the closure.
///
/// # Examples

33
tests/packages.rs Normal file
View File

@ -0,0 +1,33 @@
use rhai::{Engine, EvalAltResult, INT, Scope};
use rhai::packages::{Package, StandardPackage};
#[test]
fn test_packages() -> Result<(), Box<EvalAltResult>> {
let e = Engine::new();
let ast = e.compile("x")?;
let std_pkg = StandardPackage::new();
let make_call = |x: INT| -> Result<INT, Box<EvalAltResult>> {
// Create a raw Engine - extremely cheap.
let mut engine = Engine::new_raw();
// Load packages - cheap.
engine.load_package(std_pkg.get());
// Create custom scope - cheap.
let mut scope = Scope::new();
// Push variable into scope - relatively cheap.
scope.push("x", x);
// Evaluate script.
engine.eval_ast_with_scope::<INT>(&mut scope, &ast)
};
// The following loop creates 10,000 Engine instances!
for x in 0..10_000 {
assert_eq!(make_call(x)?, x);
}
Ok(())
}