Skip to content

Commit

Permalink
Merge pull request #227 from schungx/master
Browse files Browse the repository at this point in the history
Minor refactoring.
  • Loading branch information
schungx committed Aug 29, 2020
2 parents 99aaf8f + 402c85c commit 3892ffe
Show file tree
Hide file tree
Showing 17 changed files with 234 additions and 45 deletions.
1 change: 1 addition & 0 deletions RELEASES.md
Expand Up @@ -9,6 +9,7 @@ Bug fixes

* `Engine::compile_expression`, `Engine::eval_expression` etc. no longer parse anonymous functions and closures.
* Imported modules now work inside closures.
* Closures that capture now work under `no_object`.


Version 0.18.2
Expand Down
3 changes: 2 additions & 1 deletion doc/src/SUMMARY.md
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions doc/src/engine/func.md
@@ -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`):
Expand All @@ -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

Expand Down
6 changes: 4 additions & 2 deletions doc/src/language/fn-closure.md
Expand Up @@ -35,15 +35,17 @@ 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.

4. The variable is then converted into a **reference-counted shared value**.

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.

Expand Down
14 changes: 14 additions & 0 deletions doc/src/language/json.md
Expand Up @@ -75,3 +75,17 @@ let map = engine.parse_json(&new_json, false)?;

map.len() == 2; // 'map' contains two properties: 'a' and 'b'
```


Use `serde` to Serialize/Deserialize to/from JSON
------------------------------------------------

Remember, `Engine::parse_json` is nothing more than a _cheap_ alternative to true JSON parsing.

If correctness is needed, or for more configuration possibilities, turn on the [`serde`][features]
feature to pull in the [`serde`](https://crates.io/crates/serde) crate which enables
serialization and deserialization to/from multiple formats, including JSON.

Beware, though... the [`serde`](https://crates.io/crates/serde) crate is quite heavy.

See _[Serialization/Deserialization of `Dynamic` with `serde`][`serde`]_ for more details.
2 changes: 2 additions & 0 deletions doc/src/links.md
Expand Up @@ -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

Expand Down
70 changes: 70 additions & 0 deletions doc/src/patterns/parallel.md
@@ -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)?;
}
```
8 changes: 8 additions & 0 deletions doc/src/rust/packages/create.md
Expand Up @@ -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.


Expand Down
21 changes: 16 additions & 5 deletions doc/src/rust/serde.md
Expand Up @@ -6,15 +6,16 @@ Serialization and Deserialization of `Dynamic` with `serde`
Rhai's [`Dynamic`] type supports serialization and deserialization by [`serde`](https://crates.io/crates/serde)
via the [`serde`][features] feature.

A [`Dynamic`] can be seamlessly converted to and from a type that implements `serde::Serialize` and/or
`serde::Deserialize`.
A [`Dynamic`] can be seamlessly converted to and from a type that implements
[`serde::Serialize`](https://docs.serde.rs/serde/trait.Serialize.html) and/or
[`serde::Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html).


Serialization
-------------

The function `rhai::ser::to_dynamic` automatically converts any Rust type that implements `serde::Serialize`
into a [`Dynamic`].
The function `rhai::ser::to_dynamic` automatically converts any Rust type that implements
[`serde::Serialize`](https://docs.serde.rs/serde/trait.Serialize.html) into a [`Dynamic`].

This is usually not necessary because using [`Dynamic::from`][`Dynamic`] is much easier and is essentially
the same thing. The only difference is treatment for integer values. `Dynamic::from` will keep the different
Expand Down Expand Up @@ -64,7 +65,7 @@ Deserialization
---------------

The function `rhai::de::from_dynamic` automatically converts a [`Dynamic`] value into any Rust type
that implements `serde::Deserialize`.
that implements [`serde::Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html).

In particular, [object maps] are converted into Rust `struct`'s (or any type that is marked as
a `serde` map) while [arrays] are converted into Rust `Vec`'s (or any type that is marked
Expand Down Expand Up @@ -102,3 +103,13 @@ let result: Dynamic = engine.eval(r#"
// Convert the 'Dynamic' object map into 'MyStruct'
let x: MyStruct = from_dynamic(&result)?;
```


Lighter Alternative
-------------------

The [`serde`](https://crates.io/crates/serde) crate is quite heavy.

If only _simple_ JSON parsing (i.e. only deserialization) of a hash object into a Rhai [object map] is required,
the [`Engine::parse_json`]({{rootUrl}}/language/json.md}}) method is available as a _cheap_ alternative,
but it does not provide the same level of correctness, nor are there any configurable options.
2 changes: 1 addition & 1 deletion doc/src/rust/traits.md
Expand Up @@ -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` |
1 change: 1 addition & 0 deletions src/engine.rs
Expand Up @@ -340,6 +340,7 @@ pub fn get_script_function_by_signature<'a>(
///
/// This type is volatile and may change.
#[cfg(not(feature = "unchecked"))]
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Limits {
/// Maximum levels of call-stack to prevent infinite recursion.
///
Expand Down
20 changes: 12 additions & 8 deletions src/fn_call.rs
Expand Up @@ -52,6 +52,10 @@ use crate::stdlib::{
#[cfg(not(feature = "no_function"))]
use crate::stdlib::{collections::HashSet, string::String};

#[cfg(feature = "no_std")]
#[cfg(not(feature = "no_float"))]
use num_traits::float::Float;

/// Extract the property name from a getter function name.
#[inline(always)]
fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> {
Expand Down Expand Up @@ -1146,9 +1150,9 @@ pub fn run_builtin_binary_op(
"*" => return Ok(Some((x * y).into())),
"/" => return Ok(Some((x / y).into())),
"%" => return Ok(Some((x % y).into())),
"~" => return pow_i_i_u(x, y).map(Into::into).map(Some),
">>" => return shr_u(x, y).map(Into::into).map(Some),
"<<" => return shl_u(x, y).map(Into::into).map(Some),
"~" => return Ok(Some(x.pow(y as u32).into())),
">>" => return Ok(Some((x >> y).into())),
"<<" => return Ok(Some((x << y).into())),
_ => (),
}
}
Expand Down Expand Up @@ -1223,7 +1227,7 @@ pub fn run_builtin_binary_op(
"*" => return Ok(Some((x * y).into())),
"/" => return Ok(Some((x / y).into())),
"%" => return Ok(Some((x % y).into())),
"~" => return pow_f_f(x, y).map(Into::into).map(Some),
"~" => return Ok(Some(x.powf(y).into())),
"==" => return Ok(Some((x == y).into())),
"!=" => return Ok(Some((x != y).into())),
">" => return Ok(Some((x > y).into())),
Expand Down Expand Up @@ -1274,9 +1278,9 @@ pub fn run_builtin_op_assignment(
"*=" => return Ok(Some(*x *= y)),
"/=" => return Ok(Some(*x /= y)),
"%=" => return Ok(Some(*x %= y)),
"~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)),
">>=" => return Ok(Some(*x = shr_u(*x, y)?)),
"<<=" => return Ok(Some(*x = shl_u(*x, y)?)),
"~=" => return Ok(Some(*x = x.pow(y as u32))),
">>=" => return Ok(Some(*x = *x >> y)),
"<<=" => return Ok(Some(*x = *x << y)),
_ => (),
}
}
Expand Down Expand Up @@ -1317,7 +1321,7 @@ pub fn run_builtin_op_assignment(
"*=" => return Ok(Some(*x *= y)),
"/=" => return Ok(Some(*x /= y)),
"%=" => return Ok(Some(*x %= y)),
"~=" => return Ok(Some(*x = pow_f_f(*x, y)?)),
"~=" => return Ok(Some(*x = x.powf(y))),
_ => (),
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/fn_func.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 3892ffe

Please sign in to comment.