...
This commit is contained in:
10
rhai_engine/rhaibook/rust/modules/resolvers/built-in.md
Normal file
10
rhai_engine/rhaibook/rust/modules/resolvers/built-in.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Built-in Module Resolvers
|
||||
=========================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
There are a number of standard [module resolvers] built into Rhai, the default being the
|
||||
[`FileModuleResolver`](file.md) which simply loads a script file based on the path (with `.rhai`
|
||||
extension attached) and execute it to form a [module].
|
||||
|
||||
Built-in [module resolvers] are grouped under the `rhai::module_resolvers` module.
|
11
rhai_engine/rhaibook/rust/modules/resolvers/collection.md
Normal file
11
rhai_engine/rhaibook/rust/modules/resolvers/collection.md
Normal file
@@ -0,0 +1,11 @@
|
||||
`ModuleResolversCollection`
|
||||
===========================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
|
||||
A collection of [module resolvers].
|
||||
|
||||
[Modules] are resolved from each resolver in sequential order.
|
||||
|
||||
This is useful when multiple types of [modules] are needed simultaneously.
|
95
rhai_engine/rhaibook/rust/modules/resolvers/custom.md
Normal file
95
rhai_engine/rhaibook/rust/modules/resolvers/custom.md
Normal file
@@ -0,0 +1,95 @@
|
||||
Implement a Custom Module Resolver
|
||||
==================================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
For many applications in which Rhai is embedded, it is necessary to customize the way that [modules]
|
||||
are resolved. For instance, [modules] may need to be loaded from script texts stored in a database,
|
||||
not in the file system.
|
||||
|
||||
A module resolver must implement the [`ModuleResolver`][traits] trait, which contains only one
|
||||
required function: `resolve`.
|
||||
|
||||
When Rhai prepares to load a module, `ModuleResolver::resolve` is called with the name
|
||||
of the _module path_ (i.e. the path specified in the [`import`] statement).
|
||||
|
||||
```admonish success
|
||||
Upon success, it should return a shared [module] wrapped by `Rc` (or `Arc` under [`sync`]).
|
||||
|
||||
The module resolver should call `Module::build_index` on the target [module] before returning it.
|
||||
* This method flattens the entire module tree and _indexes_ it for fast function name resolution.
|
||||
* If the module is already indexed, calling this method has no effect.
|
||||
```
|
||||
|
||||
```admonish failure
|
||||
|
||||
* If the path does not resolve to a valid module, return `EvalAltResult::ErrorModuleNotFound`.
|
||||
|
||||
* If the module failed to load, return `EvalAltResult::ErrorInModule`.
|
||||
```
|
||||
|
||||
Example of a Custom Module Resolver
|
||||
-----------------------------------
|
||||
|
||||
```rust
|
||||
use rhai::{ModuleResolver, Module, Engine, EvalAltResult};
|
||||
|
||||
// Define a custom module resolver.
|
||||
struct MyModuleResolver {}
|
||||
|
||||
// Implement the 'ModuleResolver' trait.
|
||||
impl ModuleResolver for MyModuleResolver {
|
||||
// Only required function.
|
||||
fn resolve(
|
||||
&self,
|
||||
engine: &Engine, // reference to the current 'Engine'
|
||||
source_path: Option<&str>, // path of the parent module
|
||||
path: &str, // the module path
|
||||
pos: Position, // position of the 'import' statement
|
||||
) -> Result<Rc<Module>, Box<EvalAltResult>> {
|
||||
// Check module path.
|
||||
if is_valid_module_path(path) {
|
||||
// Load the custom module
|
||||
match load_secret_module(path) {
|
||||
Ok(my_module) => {
|
||||
my_module.build_index(); // index it
|
||||
Rc::new(my_module) // make it shared
|
||||
},
|
||||
// Return 'EvalAltResult::ErrorInModule' upon loading error
|
||||
Err(err) => Err(EvalAltResult::ErrorInModule(path.into(), Box::new(err), pos).into())
|
||||
}
|
||||
} else {
|
||||
// Return 'EvalAltResult::ErrorModuleNotFound' if the path is invalid
|
||||
Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Set the custom module resolver into the 'Engine'.
|
||||
engine.set_module_resolver(MyModuleResolver {});
|
||||
|
||||
engine.run(
|
||||
r#"
|
||||
import "hello" as foo; // this 'import' statement will call
|
||||
// 'MyModuleResolver::resolve' with "hello" as 'path'
|
||||
foo:bar();
|
||||
"#)?;
|
||||
```
|
||||
|
||||
|
||||
Advanced – `ModuleResolver::resolve_ast`
|
||||
----------------------------------------------
|
||||
|
||||
There is another function in the [`ModuleResolver`][traits] trait, `resolve_ast`, which is a
|
||||
low-level API intended for advanced usage scenarios.
|
||||
|
||||
`ModuleResolver::resolve_ast` has a default implementation that simply returns `None`,
|
||||
which indicates that this API is not supported by the [module resolver].
|
||||
|
||||
Any [module resolver] that serves [modules] based on Rhai scripts should implement
|
||||
`ModuleResolver::resolve_ast`. When called, the compiled [`AST`] of the script should be returned.
|
||||
|
||||
`ModuleResolver::resolve_ast` should not return an error if `ModuleResolver::resolve` will not.
|
||||
On the other hand, the same error should be returned if `ModuleResolver::resolve` will return one.
|
11
rhai_engine/rhaibook/rust/modules/resolvers/dummy.md
Normal file
11
rhai_engine/rhaibook/rust/modules/resolvers/dummy.md
Normal file
@@ -0,0 +1,11 @@
|
||||
`DummyModuleResolver`
|
||||
=====================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
```admonish abstract.small "Default"
|
||||
|
||||
`DummyModuleResolver` is the default for [`no_std`] or [`Engine::new_raw`][raw `Engine`].
|
||||
```
|
||||
|
||||
This [module resolver] acts as a _dummy_ and fails all module resolution calls.
|
64
rhai_engine/rhaibook/rust/modules/resolvers/dylib.md
Normal file
64
rhai_engine/rhaibook/rust/modules/resolvers/dylib.md
Normal file
@@ -0,0 +1,64 @@
|
||||
`DylibModuleResolver`
|
||||
=====================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
|
||||
~~~admonish warning.small "Requires external crate `rhai-dylib`"
|
||||
|
||||
`DylibModuleResolver` resides in the [`rhai-dylib`] crate which must be specified
|
||||
as a dependency:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rhai-dylib = { version = "0.1" }
|
||||
```
|
||||
~~~
|
||||
|
||||
```admonish danger.small "Linux or Windows only"
|
||||
|
||||
[`rhai-dylib`] currently supports only Linux and Windows.
|
||||
```
|
||||
|
||||
Parallel to how the [`FileModuleResolver`](file.md) works, `DylibModuleResolver` loads external
|
||||
native Rust [modules] from compiled _dynamic shared libraries_ (e.g. `.so` in Linux and `.dll` in
|
||||
Windows).
|
||||
|
||||
Therefore, [`FileModuleResolver`](file.md`) loads Rhai script files while `DylibModuleResolver`
|
||||
loads native Rust shared libraries. It is very common to have the two work together.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module};
|
||||
use rhai::module_resolvers::{FileModuleResolver, ModuleResolversCollection};
|
||||
use rhai_dylib::module_resolvers::DylibModuleResolver;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Use a module resolvers collection
|
||||
let mut resolvers = ModuleResolversCollection::new();
|
||||
|
||||
// First search for script files in the file system
|
||||
resolvers += FileModuleResolver::new();
|
||||
|
||||
// Then search for shared-library plugins in the file system
|
||||
resolvers += DylibModuleResolver::new();
|
||||
|
||||
// Set the module resolver into the engine
|
||||
engine.set_module_resolver(resolvers);
|
||||
|
||||
|
||||
┌─────────────┐
|
||||
│ Rhai Script │
|
||||
└─────────────┘
|
||||
|
||||
// If there is 'path/to/my_module.rhai', load it.
|
||||
// Otherwise, check for 'path/to/my_module.so' on Linux
|
||||
// ('path/to/my_module.dll' on Windows).
|
||||
import "path/to/my_module" as m;
|
||||
|
||||
m::greet();
|
||||
```
|
183
rhai_engine/rhaibook/rust/modules/resolvers/file.md
Normal file
183
rhai_engine/rhaibook/rust/modules/resolvers/file.md
Normal file
@@ -0,0 +1,183 @@
|
||||
`FileModuleResolver`
|
||||
====================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
|
||||
```admonish abstract.small "Default"
|
||||
|
||||
`FileModuleResolver` is the default for [`Engine::new`][`Engine`].
|
||||
```
|
||||
|
||||
The _default_ [module] resolution service, not available for [`no_std`] or [WASM] builds.
|
||||
Loads a script file (based off the current directory or a specified one) with `.rhai` extension.
|
||||
|
||||
|
||||
Function Namespace
|
||||
-------------------
|
||||
|
||||
All functions in the [_global_ namespace][function namespace], plus all those defined in the same
|
||||
[module], are _merged_ into a _unified_ [namespace][function namespace].
|
||||
|
||||
All [modules] imported at _global_ level via [`import`] statements become sub-[modules],
|
||||
which are also available to [functions] defined within the same script file.
|
||||
|
||||
|
||||
Base Directory
|
||||
--------------
|
||||
|
||||
```admonish tip.side.wide "Tip: Default"
|
||||
|
||||
If the base directory is not set, then relative paths are based off the directory of the loading script.
|
||||
|
||||
This allows scripts to simply cross-load each other.
|
||||
```
|
||||
|
||||
_Relative_ paths are resolved relative to a _root_ directory, which is usually the base directory.
|
||||
|
||||
The base directory can be set via `FileModuleResolver::new_with_path` or
|
||||
`FileModuleResolver::set_base_path`.
|
||||
|
||||
|
||||
Custom [`Scope`]
|
||||
----------------
|
||||
|
||||
```admonish tip.side.wide "Tip"
|
||||
|
||||
This [`Scope`] can conveniently hold global [constants] etc.
|
||||
```
|
||||
|
||||
The `set_scope` method adds an optional [`Scope`] which will be used to [optimize][script optimization] [module] scripts.
|
||||
|
||||
|
||||
Caching
|
||||
-------
|
||||
|
||||
```admonish tip.side.wide "Tip: Enable/disable caching"
|
||||
|
||||
Use `enable_cache` to enable/disable the cache.
|
||||
```
|
||||
|
||||
By default, [modules] are also _cached_ so a script file is only evaluated _once_, even when
|
||||
repeatedly imported.
|
||||
|
||||
|
||||
Unix Shebangs
|
||||
-------------
|
||||
|
||||
On Unix-like systems, the _shebang_ (`#!`) is used at the very beginning of a script file to mark a
|
||||
script with an interpreter (for Rhai this would be [`rhai-run`]({{rootUrl}}/start/bin.md)).
|
||||
|
||||
If a script file starts with `#!`, the entire first line is skipped.
|
||||
Because of this, Rhai scripts with shebangs at the beginning need no special processing.
|
||||
|
||||
```js
|
||||
#!/home/to/me/bin/rhai-run
|
||||
|
||||
// This is a Rhai script
|
||||
|
||||
let answer = 42;
|
||||
print(`The answer is: ${answer}`);
|
||||
```
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
|
||||
```rust
|
||||
┌────────────────┐
|
||||
│ my_module.rhai │
|
||||
└────────────────┘
|
||||
|
||||
// This function overrides any in the main script.
|
||||
private fn inner_message() { "hello! from module!" }
|
||||
|
||||
fn greet() {
|
||||
print(inner_message()); // call function in module script
|
||||
}
|
||||
|
||||
fn greet_main() {
|
||||
print(main_message()); // call function not in module script
|
||||
}
|
||||
|
||||
|
||||
┌───────────┐
|
||||
│ main.rhai │
|
||||
└───────────┘
|
||||
|
||||
// This function is overridden by the module script.
|
||||
fn inner_message() { "hi! from main!" }
|
||||
|
||||
// This function is found by the module script.
|
||||
fn main_message() { "main here!" }
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(); // prints "hello! from module!"
|
||||
|
||||
m::greet_main(); // prints "main here!"
|
||||
```
|
||||
|
||||
|
||||
Simulate Virtual Functions
|
||||
--------------------------
|
||||
|
||||
When calling a namespace-qualified [function] defined within a [module], other [functions] defined
|
||||
within the same module override any similar-named [functions] (with the same number of parameters)
|
||||
defined in the [global namespace][function namespace].
|
||||
|
||||
This is to ensure that a [module] acts as a self-contained unit and [functions] defined in the
|
||||
calling script do not override [module] code.
|
||||
|
||||
In some situations, however, it is actually beneficial to do it in reverse: have [module] [functions] call
|
||||
[functions] defined in the calling script (i.e. in the [global namespace][function namespace]) if
|
||||
they exist, and only call those defined in the [module] if none are found.
|
||||
|
||||
One such situation is the need to provide a _default implementation_ to a simulated _virtual_ function:
|
||||
|
||||
```rust
|
||||
┌────────────────┐
|
||||
│ my_module.rhai │
|
||||
└────────────────┘
|
||||
|
||||
// Do not do this (it will override the main script):
|
||||
// fn message() { "hello! from module!" }
|
||||
|
||||
// This function acts as the default implementation.
|
||||
private fn default_message() { "hello! from module!" }
|
||||
|
||||
// This function depends on a 'virtual' function 'message'
|
||||
// which is not defined in the module script.
|
||||
fn greet() {
|
||||
if is_def_fn("message", 0) { // 'is_def_fn' detects if 'message' is defined.
|
||||
print(message());
|
||||
} else {
|
||||
print(default_message());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
┌───────────┐
|
||||
│ main.rhai │
|
||||
└───────────┘
|
||||
|
||||
// The main script defines 'message' which is needed by the module script.
|
||||
fn message() { "hi! from main!" }
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(); // prints "hi! from main!"
|
||||
|
||||
|
||||
┌────────────┐
|
||||
│ main2.rhai │
|
||||
└────────────┘
|
||||
|
||||
// The main script does not define 'message' which is needed by the module script.
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(); // prints "hello! from module!"
|
||||
```
|
||||
|
35
rhai_engine/rhaibook/rust/modules/resolvers/index.md
Normal file
35
rhai_engine/rhaibook/rust/modules/resolvers/index.md
Normal file
@@ -0,0 +1,35 @@
|
||||
Module Resolvers
|
||||
================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
~~~admonish info.side "`import`"
|
||||
|
||||
See the section on [_Importing Modules_][`import`] for more details.
|
||||
~~~
|
||||
|
||||
When encountering an [`import`] statement, Rhai attempts to _resolve_ the [module] based on the path string.
|
||||
|
||||
_Module Resolvers_ are service types that implement the [`ModuleResolver`][traits] trait.
|
||||
|
||||
|
||||
Set into `Engine`
|
||||
-----------------
|
||||
|
||||
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
||||
|
||||
```rust
|
||||
use rhai::module_resolvers::{DummyModuleResolver, StaticModuleResolver};
|
||||
|
||||
// Create a module resolver
|
||||
let resolver = StaticModuleResolver::new();
|
||||
|
||||
// Register functions into 'resolver'...
|
||||
|
||||
// Use the module resolver
|
||||
engine.set_module_resolver(resolver);
|
||||
|
||||
// Effectively disable 'import' statements by setting module resolver to
|
||||
// the 'DummyModuleResolver' which acts as... well... a dummy.
|
||||
engine.set_module_resolver(DummyModuleResolver::new());
|
||||
```
|
26
rhai_engine/rhaibook/rust/modules/resolvers/static.md
Normal file
26
rhai_engine/rhaibook/rust/modules/resolvers/static.md
Normal file
@@ -0,0 +1,26 @@
|
||||
`StaticModuleResolver`
|
||||
======================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
|
||||
~~~admonish abstract.small "Useful for `no-std`"
|
||||
|
||||
`StaticModuleResolver` is often used with [`no_std`] in embedded environments
|
||||
without a file system.
|
||||
~~~
|
||||
|
||||
Loads [modules] that are statically added.
|
||||
|
||||
Functions are searched in the [_global_ namespace][function namespace] by default.
|
||||
|
||||
```rust
|
||||
use rhai::{Module, module_resolvers::StaticModuleResolver};
|
||||
|
||||
let module: Module = create_a_module();
|
||||
|
||||
let mut resolver = StaticModuleResolver::new();
|
||||
resolver.insert("my_module", module);
|
||||
|
||||
engine.set_module_resolver(resolver);
|
||||
```
|
Reference in New Issue
Block a user