This commit is contained in:
2025-04-03 09:18:05 +02:00
parent f6935492fb
commit 7e9ad524cc
329 changed files with 45891 additions and 0 deletions

View 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.

View 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.

View 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 &ndash; `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.

View 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.

View 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();
```

View 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!"
```

View 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());
```

View 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);
```