Add writeup on plugins.

This commit is contained in:
Stephen Chung 2020-08-30 23:13:47 +08:00
parent 512951cceb
commit 4d9aad816c
9 changed files with 416 additions and 9 deletions

View File

@ -43,7 +43,11 @@ The Rhai Scripting Language
4. [Printing Custom Types](rust/print-custom.md) 4. [Printing Custom Types](rust/print-custom.md)
9. [Packages](rust/packages/index.md) 9. [Packages](rust/packages/index.md)
1. [Built-in Packages](rust/packages/builtin.md) 1. [Built-in Packages](rust/packages/builtin.md)
2. [Create a Custom Package](rust/packages/create.md) 2. [Load a Plugin Module as a Package](rust/packages/plugin.md)
3. [Manually Create a Custom Package](rust/packages/create.md)
10. [Plugins](plugins/index.md)
1. [Create a Plugin Module](plugins/module.md)
2. [Create a Plugin Function](plugins/function.md)
5. [Rhai Language Reference](language/index.md) 5. [Rhai Language Reference](language/index.md)
1. [Comments](language/comments.md) 1. [Comments](language/comments.md)
2. [Values and Types](language/values-and-types.md) 2. [Values and Types](language/values-and-types.md)
@ -84,7 +88,7 @@ The Rhai Scripting Language
17. [Modules](language/modules/index.md) 17. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
2. [Import Modules](language/modules/import.md) 2. [Import Modules](language/modules/import.md)
3. [Create from Rust](rust/modules/index.md) 3. [Create from Rust](rust/modules/create.md)
4. [Create from AST](language/modules/ast.md) 4. [Create from AST](language/modules/ast.md)
5. [Module Resolvers](rust/modules/resolvers.md) 5. [Module Resolvers](rust/modules/resolvers.md)
1. [Custom Implementation](rust/modules/imp-resolver.md) 1. [Custom Implementation](rust/modules/imp-resolver.md)

View File

@ -33,6 +33,12 @@
[packages]: {{rootUrl}}/rust/packages/index.md [packages]: {{rootUrl}}/rust/packages/index.md
[custom package]: {{rootUrl}}/rust/packages/create.md [custom package]: {{rootUrl}}/rust/packages/create.md
[custom packages]: {{rootUrl}}/rust/packages/create.md [custom packages]: {{rootUrl}}/rust/packages/create.md
[plugin]: {{rootUrl}}/plugins/index.md
[plugins]: {{rootUrl}}/plugins/index.md
[plugin module]: {{rootUrl}}/plugins/module.md
[plugin modules]: {{rootUrl}}/plugins/module.md
[plugin function]: {{rootUrl}}/plugins/function.md
[plugin functions]: {{rootUrl}}/plugins/function.md
[`Scope`]: {{rootUrl}}/engine/scope.md [`Scope`]: {{rootUrl}}/engine/scope.md
[`serde`]: {{rootUrl}}/rust/serde.md [`serde`]: {{rootUrl}}/rust/serde.md

View File

@ -0,0 +1,78 @@
Create a Plugin Function
========================
{{#include ../links.md}}
Sometimes only a few ad hoc functions are required and it is simpler to register
individual functions instead of a full-blown [plugin module].
Macros
------
| Macro | Apply to | Behavior |
| ------------------------ | ------------------------------------------------------------- | --------------------------------------------------------- |
| `#[export_fn]` | Rust function defined in module | Export the function |
| `#[rhai_fn(return_raw)]` | Rust function returning `Result<Dynamic, Box<EvalAltResult>>` | Specify that this is a fallible function |
| `register_exported_fn!` | [`Engine`] instance, register name, function name | Register function with the [`Engine`] under specific name |
| `set_exported_fn!` | [`Module`], register name, function name | Register function with the [`Module`] under specific name |
`#[export_fn]` and `register_exported_fn!`
-----------------------------------------
Apply `#[export_fn]` onto a function defined at _module level_ to convert it into a Rhai plugin function.
The function cannot be nested inside another function - it can only be defined directly under a module.
To register the plugin function, simply call `register_exported_fn!`. The name of the function can be
any text string, so it is possible to register _overloaded_ functions as well as operators.
```rust
use rhai::plugins::*; // import macros
#[export_fn]
fn increment(num: &mut i64) {
*num += 1;
}
fn main() {
let mut engine = Engine::new();
// 'register_exported_fn!' registers the function as 'inc' with the Engine.
register_exported_fn!(engine, "inc", increment);
}
```
Fallible Functions
------------------
To register [fallible functions] (i.e. functions that may return errors), apply the
`#[rhai_fn(return_raw)]` attribute on plugin functions that return `Result<Dynamic, Box<EvalAltResult>>`.
A syntax error is generated if the function with `#[rhai_fn(return_raw)]` does not
have the appropriate return type.
```rust
use rhai::plugins::*; // import macros
#[export_fn]
#[rhai_fn(return_raw)]
pub fn double_and_divide(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
if y == 0 {
Err("Division by zero!".into())
} else {
let result = (x * 2) / y;
Ok(result.into())
}
}
fn main() {
let mut engine = Engine::new();
// Overloads the operator '+' with the Engine.
register_exported_fn!(engine, "+", double_and_divide);
}
```

11
doc/src/plugins/index.md Normal file
View File

@ -0,0 +1,11 @@
Plugins
=======
{{#include ../links.md}}
Rhai contains a robust _plugin_ system that greatly simplifies registration of custom functions.
Instead of the large `Engine::register_XXX` API, and the parallel `Module::set_fn_XXX` API,
a _plugin_ simplifies the work of creating and registering multiple functions into an [`Engine`].
Plugins are processed via a set of procedural macros under the `rhai::plugins` module.

194
doc/src/plugins/module.md Normal file
View File

@ -0,0 +1,194 @@
Create a Plugin Module
======================
{{#include ../links.md}}
The core of creating a plugin [module] is the `#[export_module]` attribute.
When applied on a module definition, `#[export_module]` automatically generates Rhai-acceptable
functions from all `pub` functions defined within.
The resulting module can then be loaded into an [`Engine`] as a normal [module],
or as a [custom package].
Macros
------
| Macro | Apply to | Behavior |
| --------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------- |
| `#[export_module]` | Rust module | Export all `pub` functions |
| `#[rhai_fn(skip)]` | Function in Rust module | Do not export this function |
| `#[rhai_fn(return_raw)]` | `pub` function in Rust module returning `Result<Dynamic, Box<EvalAltResult>>` | Specify that this is a fallible function |
| `#[rhai_fn(name = "...")]` | `pub` function in Rust module | Register function under specific name |
| `#[rhai_fn(get = "...")]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a property getter under specific name |
| `#[rhai_fn(set = "...")]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a property setter under specific name |
| `#[rhai_fn(index_get]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a index getter |
| `#[rhai_fn(index_set)]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a index setter |
| `#[rhai_mod(name = "...")]` | `pub` sub-module in Rust module | Export the sub-module under specific name |
| `exported_module!` | Rust module name | Create a [module] containing exported functions |
`#[export_module]` and `exported_module!`
----------------------------------------
Apply `#[export_module]` onto a standard module to convert all `pub` functions
into Rhai plugin functions.
```rust
use rhai::plugins::*; // import macros
#[export_module]
mod my_module {
// This function will be registered as 'greet'.
pub fn greet(name: &str) -> String {
format!("hello, {}!", name)
}
// This function will be registered as 'get_num'.
pub fn get_num() -> i64 {
mystic_number()
}
// This function will be registered as 'increment'.
pub fn increment(num: &mut i64) {
*num += 1;
}
// This function is NOT registered.
fn mystic_number() -> i64 {
42
}
}
fn main() {
let mut engine = Engine::new();
// 'exported_module!' creates the plugin module.
let module = exported_module!(my_module);
// A module can simply be loaded as a custom package.
engine.load_package(module);
}
```
The above automatically defines a plugin module named `my_module` which can be converted into
a Rhai [module] via `exported_module!`. The functions contained within the module definition
(i.e. `greet`, `get_num` and `increment`) are automatically registered into the [`Engine`] when
`Engine::load_package` is called.
```rust
let x = greet("world");
x == "hello, world!";
let x = greet(get_num().to_string());
x == "hello, 42!";
let x = get_num();
x == 42;
increment(x);
x == 43;
```
Getters, Setters and Indexers
-----------------------------
Functions can be marked as [getters/setters] and [indexers] for [custom types] via the `#[rhai_fn]`
attribute, which is applied on a function level.
```rust
use rhai::plugins::*; // import macros
#[export_module]
mod my_module {
// This is a normal function 'greet'.
pub fn greet(name: &str) -> String {
format!("hello, {}!", name)
}
// This is a getter for 'MyType::prop'.
#[rhai_fn(get = "prop")]
pub fn get_prop(obj: &mut MyType) -> i64 {
obj.prop
}
// This is a setter for 'MyType::prop'.
#[rhai_fn(set = "prop")]
pub fn set_prop(obj: &mut MyType, value: i64) {
obj.prop = value;
}
// This is an index getter for 'MyType'.
#[rhai_fn(index_get)]
pub fn get_index(obj: &mut MyType, index: i64) -> bool {
obj.list[index]
}
// This is an index setter for 'MyType'.
#[rhai_fn(index_get)]
pub fn get_index(obj: &mut MyType, index: i64, state: bool) {
obj.list[index] = state;
}
}
```
Function Overloading and Operators
---------------------------------
Operators and overloaded functions can be specified via `#[rhai_fn(name = "...")]` applied upon
individual functions.
The text string given as the `name` parameter to `#[rhai_fn]` is used to register the function with
the [`Engine`], disregarding the actual name of the function.
With `#[rhai_fn(name = "...")]`, multiple functions may be registered under the same name in Rhai.
Operators (which require function names that are not valid for Rust) can also be registered this way.
```rust
use rhai::plugins::*; // import macros
#[export_module]
mod my_module {
// This is the '+' operator for 'MyType'.
#[rhai_fn(name = "+")]
pub fn add(obj: &mut MyType, value: i64) {
obj.prop += value;
}
// This function is 'calc (i64)'.
#[rhai_fn(name = "calc")]
pub fn calc_with_default(num: i64) -> i64 {
...
}
// This function is 'calc (i64, bool)'.
#[rhai_fn(name = "calc")]
pub fn calc_with_option(num: i64, option: bool) -> i64 {
...
}
}
```
Fallible Functions
------------------
To register [fallible functions] (i.e. functions that may return errors), apply the
`#[rhai_fn(return_raw)]` attribute on functions that return `Result<Dynamic, Box<EvalAltResult>>`.
A syntax error is generated if the function with `#[rhai_fn(return_raw)]` does not
have the appropriate return type.
```rust
use rhai::plugins::*; // import macros
#[export_module]
mod my_module {
// This overloads the '/' operator for i64.
#[rhai_fn(name = "/", return_raw)]
pub fn double_and_divide(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
if y == 0 {
Err("Division by zero!".into())
} else {
let result = (x * 2) / y;
Ok(result.into())
}
}
}
```

View File

@ -3,13 +3,23 @@ Create a Module from Rust
{{#include ../../links.md}} {{#include ../../links.md}}
Manually creating a [`Module`] is possible via the `Module` API.
Create via Plugin
-----------------
By far the simplest way to create a [module] is via a [plugin module].
Create via `Module` API
-----------------------
Manually creating a [module] is possible via the `Module` API.
For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Module.html) online. For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Module.html) online.
Make the Module Available to the Engine Make the `Module` Available to the `Engine`
-------------------------------------- ------------------------------------------
In order to _use_ a custom module, there must be a [module resolver]. In order to _use_ a custom module, there must be a [module resolver].

View File

@ -1,5 +1,5 @@
Create a Custom Package Manually Create a Custom Package
====================== ===============================
{{#include ../../links.md}} {{#include ../../links.md}}
@ -13,7 +13,7 @@ Loading a package into an [`Engine`] is functionally equivalent to calling `Engi
on _each_ of the functions inside the package. But because packages are _shared_, loading an existing 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. 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!` can be used to create a new custom package.
Macro Parameters Macro Parameters
@ -53,3 +53,48 @@ def_package!(rhai:MyPackage:"My own personal super package", module, {
}); });
}); });
``` ```
Create a Custom Package from a Plugin Module
-------------------------------------------
By far the easiest way to create a custom module is to call `Module::merge_flatten` from within
`rhai::def_package!` which simply merges in all the functions defined within a [plugin module].
In fact, this exactly is how Rhai's built-in packages, such as `BasicMathPackage`, are implemented.
`rhai::plugins::exported_module!` generates a module from the [plugins][plugin module] definition,
and `Module::merge_flatten` consumes its, adding all its registered functions into the package itself.
```rust
// Import necessary types and traits.
use rhai::{
def_package,
packages::Package,
packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage}
};
use rhai::plugin::*;
// Define plugin module.
#[export_module]
mod my_module {
pub fn greet(name: &str) -> String {
format!("hello, {}!", name)
}
pub fn get_num() -> i64 {
42
}
}
// Define the package 'MyPackage'.
def_package!(rhai:MyPackage:"My own personal super package", module, {
// Aggregate existing packages simply by calling 'init' on each.
ArithmeticPackage::init(module);
LogicPackage::init(module);
BasicArrayPackage::init(module);
BasicMapPackage::init(module);
// Merge the plugin module into the custom package.
module.merge_flatten(exported_module!(my_module));
});
```

View File

@ -11,7 +11,7 @@ packages to be used.
Packages typically contain Rust functions that are callable within a Rhai script. Packages typically contain Rust functions that are callable within a Rhai script.
All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers). All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers).
Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`], Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`) among multiple instances of [`Engine`],
even across threads (under [`sync`]). Therefore, a package only has to be created _once_. even across threads (under [`sync`]). Therefore, a package only has to be created _once_.
```rust ```rust
@ -36,3 +36,21 @@ namespace alias specified in an [`import`] statement (see also [modules]).
A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with
the `import` statement). the `import` statement).
Load a Module as a Package
--------------------------
Stand-alone [modules] can be loaded directly into an [`Engine`] as a package via the same `Engine::load_package` API.
```rust
let mut module = Module::new();
:
// add functions into module
:
engine.load_package(module);
```
[Modules], however, are not _shared_, so use a [custom package] if it must be shared among multiple
instances of [`Engine`].

View File

@ -0,0 +1,41 @@
Load a Plugin Module as a Package
================================
{{#include ../../links.md}}
[Plugin modules] can be loaded as a package just like a normal [module].
```rust
use rhai::Engine;
use rhai::plugin::*;
// Define plugin module.
#[export_module]
mod my_module {
pub fn greet(name: &str) -> String {
format!("hello, {}!", name)
}
pub fn get_num() -> i64 {
42
}
}
fn main() {
let mut engine = Engine::new();
// Create plugin module.
let module = exported_module!(my_module);
// Make the 'greet' and 'get_num' functions available to scripts.
engine.load_package(module);
}
```
Share a Package Among `Engine`s
------------------------------
Loading a [module] via `Engine::load_package` consumes the module and it cannot be used again.
If the functions are needed for multiple instances of [`Engine`], create a [custom package] from the
[plugin module], which can then be shared with `Package::get`.