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) 1. [Hello World in Rhai - Evaluate a Script](engine/hello-world.md)
2. [Compile to AST for Repeated Evaluations](engine/compile.md) 2. [Compile to AST for Repeated Evaluations](engine/compile.md)
3. [Call a Rhai Function from Rust](engine/call-fn.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) 5. [Evaluate Expressions Only](engine/expressions.md)
6. [Raw Engine](engine/raw.md) 6. [Raw Engine](engine/raw.md)
4. [Extend Rhai with Rust](rust/index.md) 4. [Extend Rhai with Rust](rust/index.md)
@ -106,6 +106,7 @@ The Rhai Scripting Language
2. [Loadable Configuration](patterns/config.md) 2. [Loadable Configuration](patterns/config.md)
3. [Control Layer](patterns/control.md) 3. [Control Layer](patterns/control.md)
4. [Singleton Command](patterns/singleton.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) 2. [Capture Scope for Function Call](language/fn-capture.md)
3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md) 3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md)
4. [Script Optimization](engine/optimize/index.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}} {{#include ../links.md}}
It is possible to further encapsulate a script in Rust such that it becomes a normal Rust function. 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` Creating them is accomplished via the `Func` trait which contains `create_from_script`
(as well as its companion method `create_from_ast`): (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 "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 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, 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. 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. 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. 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 [built-in operators]: {{rootUrl}}/engine/raw.md#built-in-operators
[package]: {{rootUrl}}/rust/packages/index.md [package]: {{rootUrl}}/rust/packages/index.md
[packages]: {{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 [`Scope`]: {{rootUrl}}/rust/scope.md
[`serde`]: {{rootUrl}}/rust/serde.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. 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. 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` | | `RegisterFn` | Trait for registering functions | `register_fn` |
| `RegisterResultFn` | Trait for registering fallible functions returning `Result<Dynamic, Box<EvalAltResult>>` | `register_result_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` | | `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`. /// Currently, `Engine` is neither `Send` nor `Sync`. Use the `sync` feature to make it `Send + Sync`.
#[derive(Clone)]
pub struct Engine { pub struct Engine {
/// A unique ID identifying this scripting `Engine`. /// A unique ID identifying this scripting `Engine`.
pub id: Option<String>, pub id: Option<String>,

View File

@ -12,11 +12,11 @@ use crate::scope::Scope;
use crate::stdlib::{boxed::Box, string::ToString}; 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> { pub trait Func<ARGS, RET> {
type Output; 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. /// The `Engine` and `AST` are consumed and basically embedded into the closure.
/// ///
/// # Examples /// # Examples
@ -47,7 +47,7 @@ pub trait Func<ARGS, RET> {
/// # } /// # }
fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output; 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. /// The `Engine` is consumed and basically embedded into the closure.
/// ///
/// # Examples /// # 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(())
}