diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index f897b61e..c6457b52 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -43,7 +43,11 @@ The Rhai Scripting Language 4. [Printing Custom Types](rust/print-custom.md) 9. [Packages](rust/packages/index.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) 1. [Comments](language/comments.md) 2. [Values and Types](language/values-and-types.md) @@ -84,7 +88,7 @@ The Rhai Scripting Language 17. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.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) 5. [Module Resolvers](rust/modules/resolvers.md) 1. [Custom Implementation](rust/modules/imp-resolver.md) diff --git a/doc/src/links.md b/doc/src/links.md index e030478a..1f666afc 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -33,6 +33,12 @@ [packages]: {{rootUrl}}/rust/packages/index.md [custom package]: {{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 [`serde`]: {{rootUrl}}/rust/serde.md diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md new file mode 100644 index 00000000..f138d5c4 --- /dev/null +++ b/doc/src/plugins/function.md @@ -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>` | 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>`. + +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> { + 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); +} +``` diff --git a/doc/src/plugins/index.md b/doc/src/plugins/index.md new file mode 100644 index 00000000..353dedd3 --- /dev/null +++ b/doc/src/plugins/index.md @@ -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. diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md new file mode 100644 index 00000000..ac558c61 --- /dev/null +++ b/doc/src/plugins/module.md @@ -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>` | 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>`. + +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> { + if y == 0 { + Err("Division by zero!".into()) + } else { + let result = (x * 2) / y; + Ok(result.into()) + } + } +} +``` diff --git a/doc/src/rust/modules/index.md b/doc/src/rust/modules/create.md similarity index 81% rename from doc/src/rust/modules/index.md rename to doc/src/rust/modules/create.md index be6576bb..26441067 100644 --- a/doc/src/rust/modules/index.md +++ b/doc/src/rust/modules/create.md @@ -3,13 +3,23 @@ Create a Module from Rust {{#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. -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]. diff --git a/doc/src/rust/packages/create.md b/doc/src/rust/packages/create.md index 89f0e306..d212d38f 100644 --- a/doc/src/rust/packages/create.md +++ b/doc/src/rust/packages/create.md @@ -1,5 +1,5 @@ -Create a Custom Package -====================== +Manually Create a Custom Package +=============================== {{#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 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 @@ -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)); +}); +``` diff --git a/doc/src/rust/packages/index.md b/doc/src/rust/packages/index.md index 0b41656f..ee0daa37 100644 --- a/doc/src/rust/packages/index.md +++ b/doc/src/rust/packages/index.md @@ -11,7 +11,7 @@ packages to be used. 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). -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_. ```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 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`]. diff --git a/doc/src/rust/packages/plugin.md b/doc/src/rust/packages/plugin.md new file mode 100644 index 00000000..42bc9519 --- /dev/null +++ b/doc/src/rust/packages/plugin.md @@ -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`.