reorganize module

This commit is contained in:
Timur Gordon
2025-04-04 08:28:07 +02:00
parent 1ea37e2e7f
commit 939b6b4e57
375 changed files with 7580 additions and 191 deletions

View File

@@ -1,62 +0,0 @@
Blocking/Async Function Calls
=============================
{{#include ../links.md}}
```admonish danger.small "Warning: Async and scripting don't mix well"
Otherwise, you reinvent the [_Callback Hell_](https://en.wiktionary.org/wiki/callback_hell)
which is JavaScript before all the async extensions.
```
```admonish info "Usage scenarios"
* A system's API contains async functions.
```
```admonish abstract "Key concepts"
* This pattern is based upon the _[Multi-Threaded Synchronization](multi-threading.md)_ pattern.
* An independent thread is used to run the scripting [`Engine`].
* An MPSC channel (or any other appropriate synchronization primitive) is used to send function call
arguments, packaged as a message, to another Rust thread that will perform the actual async calls.
* Results are marshaled back to the [`Engine`] thread via another MPSC channel.
```
Implementation
--------------
```admonish info.side "See also"
See the _[Multi-Threaded Synchronization](multi-threading.md)_ pattern.
```
1. Spawn a thread to run the scripting [`Engine`]. Usually the [`sync`] feature is
_NOT_ used for this pattern.
2. Spawn another thread (the `worker` thread) that can perform the actual async calls in Rust.
This thread may actually be the main thread of the program.
3. Create a pair of MPSC channels (named `command` and `reply` below) for full-duplex
communications between the two threads.
4. Register async API function to the [`Engine`] with a closure that captures the MPSC end-points.
5. If there are more than one async function, the receive end-point on the `reply` channel can simply be cloned.
The send end-point on the `command` channel can be wrapped in an `Arc<Mutex<Channel>>` for shared access.
6. In the async function, the name of the function and call arguments are serialized into JSON
(or any appropriate message format) and sent to `command` channel, where they'll be removed
by the `worker` thread and the appropriate async function called.
7. The [`Engine`] blocks on the function call, waiting for a reply message on the `reply` channel.
8. When the async function call complete on the `worker` thread, the result is sent back to
the [`Engine`] thread via the `reply` channel.
9. After the result is obtained from the `reply` channel, the [`Engine`] returns it as the return value
of the function call, ending the block and continuing evaluation.

View File

@@ -1,307 +0,0 @@
Builder Pattern / Fluent API
============================
{{#include ../links.md}}
```admonish info "Usage scenario"
* An API uses the [Builder Pattern](https://en.wikipedia.org/wiki/Builder_pattern) or a [fluent API](https://en.wikipedia.org/wiki/Fluent_interface).
* The builder type is not necessarily `Clone`.
```
```admonish abstract "Key concepts"
* Wrap the builder type in shared interior mutability (aka `Rc<RefCell<T>>` or `Arc<RwLock<T>>`).
```
```admonish tip.small "Tip: Fluent API"
This same pattern can be used to implement any [_fluent API_](https://en.wikipedia.org/wiki/Fluent_interface).
```
Implementation With Clonable Builder Type
-----------------------------------------
This assumes that the builder type implements `Clone`.
This is the most common scenario.
```rust
/// Builder for `Foo` instances.
#[derive(Clone)]
pub struct FooBuilder {
/// The `foo` option.
foo: i64,
/// The `bar` option.
bar: bool,
/// The `baz` option.
baz: String,
}
/// `FooBuilder` API which uses moves.
impl FooBuilder {
/// Creates a new builder for `Foo`.
pub fn new() -> Self {
Self { foo: 0, bar: false, baz: String::new() }
}
/// Sets the `foo` option.
pub fn with_foo(mut self, foo: i64) -> Self {
self.foo = foo;
self
}
/// Sets the `bar` option.
pub fn with_bar(mut self, bar: bool) -> Self {
self.bar = bar;
self
}
/// Sets the `baz` option.
pub fn with_baz(mut self, baz: &str) -> Self {
self.baz = baz.to_string();
self
}
/// Builds the `Foo` instance.
pub fn build(self) -> Foo {
Foo { foo: self.foo, bar: self.bar, baz: self.baz }
}
}
let mut engine = Engine::new();
engine
.register_fn("get_foo", FooBuilder::new)
.register_fn("with_foo", FooBuilder::with_foo)
.register_fn("with_bar", FooBuilder::with_bar)
.register_fn("with_baz", FooBuilder::with_baz)
.register_fn("create", FooBuilder::build);
```
Implementation With Mutable Reference
-------------------------------------
This assumes that the builder type's API uses mutable references.
The builder type does not need to implement `Clone`.
```rust
use rhai::plugin::*;
/// Builder for `Foo` instances.
/// Notice that this type does not need to be `Clone`.
pub struct FooBuilder {
/// The `foo` option.
foo: i64,
/// The `bar` option.
bar: bool,
/// The `baz` option.
baz: String,
}
/// Builder type API uses mutable references.
impl FooBuilder {
/// Creates a new builder for `Foo`.
pub fn new() -> Self {
Self { foo: 0, bar: false, baz: String::new() }
}
/// Sets the `foo` option.
pub fn with_foo(&mut self, foo: i64) -> &mut Self {
self.foo = foo; self
}
/// Sets the `bar` option.
pub fn with_bar(&mut self, bar: bool) -> &mut Self {
self.bar = bar; self
}
/// Sets the `baz` option.
pub fn with_baz(&mut self, baz: &str) -> &mut Self {
self.baz = baz.to_string(); self
}
/// Builds the `Foo` instance.
pub fn build(&self) -> Foo {
Foo { foo: self.foo, bar: self.bar, baz: self.baz.clone() }
}
}
/// Builder for `Foo`.
#[export_module]
pub mod foo_builder {
use super::{Foo, FooBuilder as BuilderImpl};
use std::cell::RefCell;
use std::rc::Rc;
/// The builder for `Foo`.
// This type is `Clone`.
pub type FooBuilder = Rc<RefCell<super::BuilderImpl>>;
/// Creates a new builder for `Foo`.
pub fn default() -> FooBuilder {
Rc::new(RefCell::new(BuilderImpl::new()))
}
/// Sets the `foo` option.
#[rhai_fn(global, pure)]
pub fn with_foo(builder: &mut FooBuilder, foo: i64) -> FooBuilder {
builder.set_foo(foo);
builder.clone()
}
/// Sets the `bar` option.
#[rhai_fn(global, pure)]
pub fn with_bar(builder: &mut FooBuilder, bar: bool) -> FooBuilder {
builder.set_bar(bar);
builder.clone()
}
/// Sets the `baz` option.
#[rhai_fn(global, pure)]
pub fn with_baz(builder: &mut FooBuilder, baz: &str) -> FooBuilder {
builder.set_baz(baz);
builder.clone()
}
/// Builds the `Foo` instance.
#[rhai_fn(global, pure)]
pub fn create(builder: &mut FooBuilder) -> Foo {
builder.borrow().build()
}
}
```
Implementation With Moves
-------------------------
What if the builder type's API relies on moves instead of mutable references?
And the builder type does not implement `Clone`?
Not too worry: the following trick has you covered!
```rust
use rhai::plugin::*;
/// Builder for `Foo` instances.
/// Notice that this type does not need to be `Clone`.
pub struct FooBuilder {
/// The `foo` option.
foo: i64,
/// The `bar` option.
bar: bool,
/// The `baz` option.
baz: String,
}
/// `FooBuilder` API which uses moves.
impl FooBuilder {
/// Creates a new builder for `Foo`.
pub fn new() -> Self {
Self { foo: 0, bar: false, baz: String::new() }
}
/// Sets the `foo` option.
pub fn with_foo(mut self, foo: i64) -> Self {
self.foo = foo;
self
}
/// Sets the `bar` option.
pub fn with_bar(mut self, bar: bool) -> Self {
self.bar = bar;
self
}
/// Sets the `baz` option.
pub fn with_baz(mut self, baz: &str) -> Self {
self.baz = baz.to_string();
self
}
/// Builds the `Foo` instance.
pub fn build(self) -> Foo {
Foo { foo: self.foo, bar: self.bar, baz: self.baz }
}
}
/// Builder for `Foo`.
#[export_module]
pub mod foo_builder {
use super::{Foo, FooBuilder as BuilderImpl};
use std::cell::RefCell;
use std::mem;
use std::rc::Rc;
/// The builder for `Foo`.
// This type is `Clone`.
// An `Option` is used for easy extraction of the builder type.
// If it is `None` then the builder is already consumed.
pub type FooBuilder = Rc<RefCell<Option<BuilderImpl>>>;
/// Creates a new builder for `Foo`.
pub fn default() -> FooBuilder {
Rc::new(RefCell::new(Some(BuilderImpl::new())))
}
/// Sets the `foo` option.
#[rhai_fn(return_raw, global, pure)]
pub fn with_foo(builder: &mut FooBuilder, foo: i64) -> Result<FooBuilder, Box<EvalAltResult>> {
let b = &mut *builder.borrow_mut();
if let Some(obj) = mem::take(b) {
*b = Some(obj.with_foo(foo));
Ok(builder.clone())
} else {
Err("Builder is already consumed".into())
}
}
/// Sets the `bar` option.
#[rhai_fn(return_raw, global, pure)]
pub fn with_bar(builder: &mut FooBuilder, bar: bool) -> Result<FooBuilder, Box<EvalAltResult>> {
let b = &mut *builder.borrow_mut();
if let Some(obj) = mem::take(b) {
*b = Some(obj.with_bar(bar));
Ok(builder.clone())
} else {
Err("Builder is already consumed".into())
}
}
/// Sets the `baz` option.
#[rhai_fn(return_raw, global, pure)]
pub fn with_baz(builder: &mut FooBuilder, baz: &str) -> Result<FooBuilder, Box<EvalAltResult>> {
let b = &mut *builder.borrow_mut();
if let Some(obj) = mem::take(b) {
*b = Some(obj.with_baz(baz));
Ok(builder.clone())
} else {
Err("Builder is already consumed".into())
}
}
/// Builds the `Foo` instance.
#[rhai_fn(return_raw, global, pure)]
pub fn create(builder: &mut FooBuilder) -> Result<Foo, Box<EvalAltResult>> {
let b = &mut *builder.borrow_mut();
if let Some(obj) = mem::take(b) {
Ok(obj.build())
} else {
Err("Builder is already consumed".into())
}
}
}
```
Usage
-----
It is easy to see that the Rhai script API mirrors the Rust API almost perfectly.
```rust
┌──────┐
│ Rust │
└──────┘
let mut engine = Engine::new();
engine.register_static_module("Foo", exported_module!(foo_builder).into());
let foo = FooBuilder::new().with_foo(42).with_bar(true).with_baz("Hello").build();
┌─────────────┐
│ Rhai script │
└─────────────┘
let foo = Foo::default().with_foo(42).with_bar(true).with_baz("Hello").create();
```

View File

@@ -1,171 +0,0 @@
Loadable Configuration
======================
{{#include ../links.md}}
```admonish info "Usage scenario"
* A system where settings and configurations are complex and logic-driven.
* Where said system is too complex to configure via standard configuration file formats such as
`JSON`, `TOML` or `YAML`.
* The system is complex enough to require a full programming language to configure.
Essentially _configuration by code_.
* Yet the configuration must be flexible, late-bound and dynamically loadable, just like a
configuration file.
```
```admonish abstract "Key concepts"
* Leverage the loadable [modules] of Rhai. The [`no_module`] feature must not be on.
* Expose the configuration API. Use separate scripts to configure that API.
Dynamically load scripts via the `import` statement.
* Leverage [function overloading] to simplify the API design.
* Since Rhai is _sand-boxed_, it cannot mutate the environment. To modify the external
configuration object via an API, it must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for
[`sync`]) and shared to the [`Engine`].
```
Implementation
--------------
### Configuration type
```rust
#[derive(Debug, Clone, Default)]
struct Config {
id: String,
some_field: i64,
some_list: Vec<String>,
some_map: HashMap<String, bool>,
}
```
### Make shared object
```rust
type SharedConfig = Rc<RefCell<Config>>;
let config = SharedConfig::default();
```
or in multi-threaded environments with the [`sync`] feature, use one of the following:
```rust
type SharedConfig = Arc<RwLock<Config>>;
type SharedConfig = Arc<Mutex<Config>>;
```
### Register config API
The trick to building a Config API is to clone the shared configuration object and move it into each
function registration via a closure.
Therefore, it is not possible to use a [plugin module] to achieve this, and each function must be
registered one after another.
```rust
// Notice 'move' is used to move the shared configuration object into the closure.
let cfg = config.clone();
engine.register_fn("config_set_id", move |id: String| cfg.borrow_mut().id = id);
let cfg = config.clone();
engine.register_fn("config_get_id", move || cfg.borrow().id.clone());
let cfg = config.clone();
engine.register_fn("config_set", move |value: i64| cfg.borrow_mut().some_field = value);
// Remember Rhai functions can be overloaded when designing the API.
let cfg = config.clone();
engine.register_fn("config_add", move |value: String|
cfg.borrow_mut().some_list.push(value)
);
let cfg = config.clone();
engine.register_fn("config_add", move |values: &mut Array|
cfg.borrow_mut().some_list.extend(values.into_iter().map(|v| v.to_string()))
);
let cfg = config.clone();
engine.register_fn("config_add", move |key: String, value: bool|
cfg.borrow_mut().some_map.insert(key, value)
);
let cfg = config.clone();
engine.register_fn("config_contains", move |value: String|
cfg.borrow().some_list.contains(&value)
);
let cfg = config.clone();
engine.register_fn("config_is_set", move |value: String|
cfg.borrow().some_map.get(&value).cloned().unwrap_or(false)
);
```
### Configuration script
```rust
┌────────────────┐
│ my_config.rhai │
└────────────────┘
config_set_id("hello");
config_add("foo"); // add to list
config_add("bar", true); // add to map
if config_contains("hey") || config_is_set("hey") {
config_add("baz", false); // add to map
}
```
### Load the configuration
```rust
import "my_config"; // run configuration script without creating a module
let id = config_get_id();
id == "hello";
```
Consider a Custom Syntax
------------------------
This is probably one of the few scenarios where a [custom syntax] can be recommended.
A properly-designed [custom syntax] can make the configuration file clean, simple to write,
easy to understand and quick to modify.
For example, the above configuration example may be expressed by this custom syntax:
```rust
┌────────────────┐
│ my_config.rhai │
└────────────────┘
// Configure ID
id "hello";
// Add to list
list + "foo";
// Add to map
map "bar" => true;
if config contains "hey" || config is_set "hey" {
map "baz" => false;
}
```
Notice that `contains` and `is_set` may also be implemented as a [custom operator].

View File

@@ -1,168 +0,0 @@
Global Constants
================
{{#include ../links.md}}
```admonish info "Usage scenario"
* Script has a lot of duplicated [constants] used inside [functions].
* For easier management, [constants] are declared at the top of the script.
* As Rhai [functions] are pure, they cannot access [constants] declared at global level
except through [`global`].
* Sprinkling large number of [`global::CONSTANT`][`global`] throughout the script makes
it slow and cumbersome.
* Using [`global`] or a [variable resolver] defeats
[constants propagation]({{rootUrl}}/engine/optimize/constants.md) in [script optimization].
```
```admonish abstract "Key concepts"
* The key to global [constants] is to use them to [optimize][script optimization] a script.
Otherwise, it would be just as simple to pass the constants into a custom [`Scope`] instead.
* The script is first compiled into an [`AST`], and all [constants] are extracted.
* The [constants] are then supplied to [re-optimize][script optimization] the [`AST`].
* This pattern also works under [_Strict Variables Mode_][strict variables].
```
Example
-------
Assume that the following Rhai script needs to work (but it doesn't).
```rust
// These are constants
const FOO = 1;
const BAR = 123;
const MAGIC_NUMBER = 42;
fn get_magic() {
MAGIC_NUMBER // <- oops! 'MAGIC_NUMBER' not found!
}
fn calc_foo(x) {
x * global::FOO // <- works but cumbersome; not desirable!
}
let magic = get_magic() * BAR;
let x = calc_foo(magic);
print(x);
```
Step 1 &ndash; Compile Script into `AST`
----------------------------------------
Compile the script into [`AST`] form.
Normally, it is useful to disable [optimizations][script optimization] at this stage since
the [`AST`] will be re-optimized later.
[_Strict Variables Mode_][strict variables] must be OFF for this to work.
```rust
// Turn Strict Variables Mode OFF (if necessary)
engine.set_strict_variables(false);
// Turn optimizations OFF
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile("...")?;
```
Step 2 &ndash; Extract Constants
--------------------------------
Use [`AST::iter_literal_variables`](https://docs.rs/rhai/{{version}}/rhai/struct.AST.html#method.iter_literal_variables)
to extract top-level [constants] from the [`AST`].
```rust
let mut scope = Scope::new();
// Extract all top-level constants without running the script
ast.iter_literal_variables(true, false).for_each(|(name, _, value)|
scope.push_constant(name, value);
);
// 'scope' now contains: FOO, BAR, MAGIC_NUMBER
```
Step 3a &ndash; Propagate Constants
-----------------------------------
[Re-optimize][script optimization] the [`AST`] using the new constants.
```rust
// Turn optimization back ON
engine.set_optimization_level(OptimizationLevel::Simple);
let ast = engine.optimize_ast(&scope, ast, engine.optimization_level());
```
Step 3b &ndash; Recompile Script (Alternative)
----------------------------------------------
If [_Strict Variables Mode_][strict variables] is used, however, it is necessary to re-compile the
script in order to detect undefined [variable] usages.
```rust
// Turn Strict Variables Mode back ON
engine.set_strict_variables(true);
// Turn optimization back ON
engine.set_optimization_level(OptimizationLevel::Simple);
// Re-compile the script using constants in 'scope'
let ast = engine.compile_with_scope(&scope, "...")?;
```
Step 4 &ndash; Run the Script
-----------------------------
At this step, the [`AST`] is now optimized with constants propagated into all access sites.
The script essentially becomes:
```rust
// These are constants
const FOO = 1;
const BAR = 123;
const MAGIC_NUMBER = 42;
fn get_magic() {
42 // <- constant replaced by value
}
fn calc_foo(x) {
x * global::FOO
}
let magic = get_magic() * 123; // <- constant replaced by value
let x = calc_foo(magic);
print(x);
```
Run it via `Engine::run_ast` or `Engine::eval_ast`.
```rust
// The 'scope' is no longer necessary
engine.run_ast(&ast)?;
```

View File

@@ -1,145 +0,0 @@
Scriptable Control Layer Over Rust Backend
==========================================
{{#include ../links.md}}
```admonish info "Usage scenario"
* A system provides core functionalities, but no driving logic.
* The driving logic must be dynamic and hot-loadable.
* A script is used to drive the system and provide control intelligence.
```
```admonish abstract "Key concepts"
* Expose a Control API.
* Leverage [function overloading] to simplify the API design.
* Since Rhai is _[sand-boxed]_, it cannot mutate anything outside of its internal environment.
To perform external actions via an API, the actual system must be wrapped in a `RefCell`
(or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`].
```
```admonish danger "Using Rhai for games"
Although this usage pattern appears a perfect fit for _game_ logic, avoid writing the _entire game_
in Rhai. Performance will not be acceptable.
Implement as much functionalities of the game engine in Rust as possible. Rhai integrates well with
Rust so this is usually not a hinderance.
Lift as much out of Rhai as possible. Use Rhai only for the logic that _must_ be dynamic or
hot-loadable.
```
Implementation
--------------
There are two broad ways for Rhai to control an external system, both of which involve wrapping the
system in a shared, interior-mutated object.
This is one way which does not involve exposing the data structures of the external system,
but only through exposing an abstract API primarily made up of functions.
Use this when the API is relatively simple and clean, and the number of functions is small enough.
For a complex API involving lots of functions, or an API that has a clear object structure, use the
[Singleton Command Object]({{rootUrl}}/patterns/singleton.md) pattern instead.
### Functional API
Assume that a system provides the following functional API:
```rust
struct EnergizerBunny;
impl EnergizerBunny {
pub fn new () -> Self { ... }
pub fn go (&mut self) { ... }
pub fn stop (&mut self) { ... }
pub fn is_going (&self) { ... }
pub fn get_speed (&self) -> i64 { ... }
pub fn set_speed (&mut self, speed: i64) { ... }
}
```
### Wrap API in shared object
```rust
pub type SharedBunny = Rc<RefCell<EnergizerBunny>>;
```
or in multi-threaded environments with the [`sync`] feature, use one of the following:
```rust
pub type SharedBunny = Arc<RwLock<EnergizerBunny>>;
pub type SharedBunny = Arc<Mutex<EnergizerBunny>>;
```
### Register control API
The trick to building a Control API is to clone the shared API object and
move it into each function registration via a closure.
Therefore, it is not possible to use a [plugin module] to achieve this, and each function must be
registered one after another.
```rust
// Notice 'move' is used to move the shared API object into the closure.
let b = bunny.clone();
engine.register_fn("bunny_power", move |on: bool| {
if on {
if b.borrow().is_going() {
println!("Still going...");
} else {
b.borrow_mut().go();
}
} else {
if b.borrow().is_going() {
b.borrow_mut().stop();
} else {
println!("Already out of battery!");
}
}
});
let b = bunny.clone();
engine.register_fn("bunny_is_going", move || b.borrow().is_going());
let b = bunny.clone();
engine.register_fn("bunny_get_speed", move ||
if b.borrow().is_going() { b.borrow().get_speed() } else { 0 }
);
let b = bunny.clone();
engine.register_fn("bunny_set_speed", move |speed: i64| -> Result<_, Box<EvalAltResult>>
if speed <= 0 {
return Err("Speed must be positive!".into());
} else if speed > 100 {
return Err("Bunny will be going too fast!".into());
}
if b.borrow().is_going() {
b.borrow_mut().set_speed(speed)
} else {
return Err("Bunny is not yet going!".into());
}
Ok(())
);
```
### Use the API
```rust
if !bunny_is_going() { bunny_power(true); }
if bunny_get_speed() > 50 { bunny_set_speed(50); }
```

View File

@@ -1,139 +0,0 @@
Domain-Specific Tools
=====================
{{#include ../links.md}}
[bin tool]: {{rootUrl}}/start/bin.md
[bin tools]: {{rootUrl}}/start/bin.md
```admonish info "Usage scenario"
* A system has a _domain-specific_ API, requiring [custom types] and/or
Rust [functions]({{rootUrl}}/rust/functions.md) to be registered and exposed to scripting.
* The system's behavior is controlled by Rhai script [functions], such as in the
[_Scriptable Event Handler with State_](events.md), [_Control Layer_](control.md) or
[_Singleton Command Object_](singleton.md) patterns.
* It is desirable to be able to _interactively_ test the system with different scripts,
and/or [debug][debugger] them.
```
```admonish abstract "Key concepts"
* Leverage the pre-packaged [bin tools] &ndash; it is easy because each of them is
one single source file.
* Modify the [`Engine`] creation code to include domain-specific registrations.
```
Implementation
--------------
### Copy necessary tool source files
Each [bin tool] is a single source file.
Download the necessary one(s) into the project's `bin` or `example` subdirectory.
| Select this source file | To make |
| --------------------------------------------------- | ------------------------------------- |
| [`rhai-run.rs`]({{repoHome}}/src/bin/rhai-run.rs) | a simple script runner for the system |
| [`rhai-repl.rs`]({{repoHome}}/src/bin/rhai-repl.rs) | an interactive REPL for the system |
| [`rhai-dbg.rs`]({{repoHome}}/src/bin/rhai-dbg.rs) | a script debugger for the system |
#### Example
```sh
rhai-run.rs -> /path/to/my_project/examples/test.rs
rhai-repl.rs -> /path/to/my_project/examples/repl.rs
rhai-dbg.rs -> /path/to/my_project/examples/db.rs
```
### Leverage `Engine` configuration code in the project
Assume the project already contains configuration code for a customized [`Engine`].
```rust
use rhai::Engine;
use rhai::plugin::*;
// Domain-specific data types
use my_project::*;
#[export_module]
mod my_domain_api {
:
:
// plugin module
:
:
}
// This function creates a new Rhai scripting engine and
// configures it properly
fn create_scripting_engine(config: MySystemConfig) -> Engine {
let mut engine = Engine::new();
// Register domain-specific API into the Engine
engine.register_type_with_name::<MyObject>("MyObject")
.register_type_with_name::<MyOtherObject>("MyOtherObject")
.register_fn(...)
.register_fn(...)
.register_fn(...)
:
:
// register API functions
:
:
.register_get_set(...)
.register_index_get_set(...)
.register_fn(...);
// Plugin modules can be used to easily and quickly
// register an entire API
engine.register_global_module(exported_module!(my_domain_api).into());
// Configuration options in 'MySystemConfig' may be used
// to control the Engine's behavior
if config.strict_mode {
engine.set_strict_variables(true);
}
// Return the scripting engine
engine
}
```
### Modify `Engine` creation
Each [bin tool] contains a line that creates the main script [`Engine`].
Modify it to call the project's creation function.
```rust
// Initialize scripting engine
let mut engine = Engine::new();
// Modify to this:
let mut engine = create_scripting_engine(my_config);
```
### Make sure that Rhai has the appropriate feature(s)
In the project's `Cargo.toml`, specify the Rhai dependency with `bin-features`.
```toml
[dependencies]
rhai = { version = "{{version}}", features = ["bin-features"] }
```
### Rebuild
Rebuild the project, which should automatically build all the customized tools.
### Run tools
Each customized tool now has access to the entire domain-specific API.
Functions and [custom types] can be used in REPL, [debugging][debugger] etc.

View File

@@ -1,55 +0,0 @@
Dynamic Constants Provider
==========================
{{#include ../links.md}}
```admonish info "Usage scenario"
* A system has a _large_ number of constants, but only a minor set will be used by any script.
* The system constants are expensive to load.
* The system constants set is too massive to push into a custom [`Scope`].
* The values of system constants are volatile and call-dependent.
```
```admonish abstract "Key concepts"
* Use a [variable resolver] to intercept variable access.
* Only load a variable when it is being used.
* Perform a lookup based on variable name, and provide the correct data value.
* May even perform back-end network access or look up the latest value from a database.
```
Implementation
--------------
```rust
let mut engine = Engine::new();
// Create shared data provider.
// Assume that SystemValuesProvider::get(&str) -> Option<value> gets a value.
let provider = Arc::new(SystemValuesProvider::new());
// Clone the shared provider
let db = provider.clone();
// Register a variable resolver.
// Move the shared provider into the closure.
engine.on_var(move |name, _, _| Ok(db.get(name).map(Dynamic::from)));
```
```admonish note.small "Values are constants"
All values provided by a [variable resolver] are _[constants]_ due to their dynamic nature.
They cannot be assigned to.
In order to change values in an external system, register a dedicated API for that purpose.
```

View File

@@ -1,281 +0,0 @@
Working With Rust Enums
=======================
{{#include ../links.md}}
```admonish question.side.wide "Why enums are hard"
Rust enum variants are not considered separate types.
Although Rhai integrates fine with Rust enums (treated transparently as [custom types]),
it is impossible (short of registering a complete API) to distinguish between individual
variants and to extract internal data from them.
```
Enums in Rust can hold data and are typically used with _pattern matching_.
Unlike Rust, Rhai does not have built-in pattern matching capabilities, so working with enum
variants that contain embedded data is not an easy proposition.
Since Rhai is dynamic and [variables] can hold any type of data, they are essentially enums
by nature.
Multiple distinct types can be stored in a single [`Dynamic`] without merging them into an enum
as variants.
This section outlines a number of possible solutions to work with Rust enums.
Simulate an Enum API
--------------------
A [plugin module] is extremely handy in creating an entire API for a custom enum type.
```rust
use rhai::plugin::*;
use rhai::{Dynamic, Engine, EvalAltResult};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum MyEnum {
Foo,
Bar(i64),
Baz(String, bool),
}
// Create a plugin module with functions constructing the 'MyEnum' variants
#[export_module]
mod MyEnumModule {
// Constructors for 'MyEnum' variants
/// `MyEnum::Foo` with no inner data.
pub const Foo: MyEnum = MyEnum::Foo;
/// `MyEnum::Bar(value)`
pub fn Bar(value: i64) -> MyEnum { MyEnum::Bar(value) }
/// `MyEnum::Baz(name, flag)`
pub fn Baz(name: String, flag: bool) -> MyEnum { MyEnum::Baz(name, flag) }
/// Return the current variant of `MyEnum`.
#[rhai_fn(global, get = "enum_type", pure)]
pub fn get_type(my_enum: &mut MyEnum) -> String {
match my_enum {
MyEnum::Foo => "Foo".to_string(),
MyEnum::Bar(_) => "Bar".to_string(),
MyEnum::Baz(_, _) => "Baz".to_string()
}
}
/// Return the inner value.
#[rhai_fn(global, get = "value", pure)]
pub fn get_value(my_enum: &mut MyEnum) -> Dynamic {
match my_enum {
MyEnum::Foo => Dynamic::UNIT,
MyEnum::Bar(x) => Dynamic::from(x),
MyEnum::Baz(_, f) => Dynamic::from(f),
}
}
// Access to inner values by position
/// Return the value kept in the first position of `MyEnum`.
#[rhai_fn(global, get = "field_0", pure)]
pub fn get_field_0(my_enum: &mut MyEnum) -> Dynamic {
match my_enum {
MyEnum::Foo => Dynamic::UNIT,
MyEnum::Bar(x) => Dynamic::from(x),
MyEnum::Baz(x, _) => Dynamic::from(x)
}
}
/// Return the value kept in the second position of `MyEnum`.
#[rhai_fn(global, get = "field_1", pure)]
pub fn get_field_1(my_enum: &mut MyEnum) -> Dynamic {
match my_enum {
MyEnum::Foo | MyEnum::Bar(_) => Dynamic::UNIT,
MyEnum::Baz(_, x) => Dynamic::from(x)
}
}
// Printing
#[rhai_fn(global, name = "to_string", name = "to_debug", pure)]
pub fn to_string(my_enum: &mut MyEnum) -> String {
format!("{my_enum:?}")
}
// '==' and '!=' operators
#[rhai_fn(global, name = "==", pure)]
pub fn eq(my_enum: &mut MyEnum, my_enum2: MyEnum) -> bool {
my_enum == &my_enum2
}
#[rhai_fn(global, name = "!=", pure)]
pub fn neq(my_enum: &mut MyEnum, my_enum2: MyEnum) -> bool {
my_enum != &my_enum2
}
}
let mut engine = Engine::new();
// Load the module as the module namespace "MyEnum"
engine.register_type_with_name::<MyEnum>("MyEnum")
.register_static_module("MyEnum", exported_module!(MyEnumModule).into());
```
With this API in place, working with enums feels almost the same as in Rust:
```rust
let x = MyEnum::Foo;
let y = MyEnum::Bar(42);
let z = MyEnum::Baz("hello", true);
x == MyEnum::Foo;
y != MyEnum::Bar(0);
// Detect enum types
x.enum_type == "Foo";
y.enum_type == "Bar";
z.enum_type == "Baz";
// Extract enum fields
x.value == ();
y.value == 42;
z.value == ();
x.name == ();
y.name == ();
z.name == "hello";
y.field_0 == 42;
y.field_1 == ();
z.field_0 == "hello";
z.field_1 == true;
```
~~~admonish tip.small "Tip: Use a macro"
For enums containing only variants with no inner data, it is convenient to use a simple macro to create
such a [plugin module].
```rust
// The 'create_enum_module!' macro
macro_rules! create_enum_module {
($module:ident : $typ:ty => $($variant:ident),+) => {
#[export_module]
pub mod $module {
$(
#[allow(non_upper_case_globals)]
pub const $variant: $typ = <$typ>::$variant;
)*
}
};
}
// The enum
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum MyEnum { Foo, Bar, Baz, Hello, World }
// This creates a plugin module called 'my_enum_module'
expand_enum! { my_enum_module: MyEnum => Foo, Bar, Baz, Hello, World }
```
~~~
Use Enums With `switch`
-----------------------
Since enums are internally treated as [custom types], they are not _literals_ and cannot be used as
a match case in [`switch`] statements. This is quite a limitation because the equivalent `match`
statement is commonly used in Rust to work with enums and bind variables to variant-internal data.
It is possible, however, to [`switch`] through enum variants based on their types:
```c , no_run
switch my_enum.enum_type {
"Foo" => ...,
"Bar" => {
let value = foo.value;
...
}
"Baz" => {
let name = foo.name;
let flag = foo.flag;
...
}
}
```
Use `switch` Through Arrays
---------------------------
Another way to work with Rust enums in a [`switch`] statement is through exposing the internal data
(or at least those that act as effective _discriminants_) of each enum variant as a variable-length
[array], usually with the name of the variant as the first item for convenience:
```rust
use rhai::Array;
engine.register_get("enum_data", |my_enum: &mut MyEnum| {
match my_enum {
MyEnum::Foo => vec![ "Foo".into() ] as Array,
// Say, skip the data field because it is not
// used as a discriminant
MyEnum::Bar(value) => vec![ "Bar".into() ] as Array,
// Say, all fields act as discriminants
MyEnum::Baz(name, flag) => vec![
"Baz".into(), name.clone().into(), (*flag).into()
] as Array
}
});
```
Then it is a simple matter to match an enum via a [`switch`] expression.
```c , no_run
// Assume 'value' = 'MyEnum::Baz("hello", true)'
// 'enum_data' creates a variable-length array with 'MyEnum' data
let x = switch value.enum_data {
["Foo"] => 1,
["Bar"] => value.field_1,
["Baz", "hello", false] => 4,
["Baz", "hello", true] => 5,
_ => 9
};
x == 5;
// Which is essentially the same as:
let x = switch [value.type, value.field_0, value.field_1] {
["Foo", (), ()] => 1,
["Bar", 42, ()] => 42,
["Bar", 123, ()] => 123,
:
["Baz", "hello", false] => 4,
["Baz", "hello", true] => 5,
_ => 9
}
```
Usually, a helper method returns an [array] of values that can uniquely determine the [`switch`] case
based on actual usage requirements &ndash; which means that it probably skips fields that contain
data instead of discriminants.
Then [`switch`] is used to very quickly match through a large number of [array] shapes and jump to the
appropriate case implementation.
Data fields can then be extracted from the enum independently.

View File

@@ -1,148 +0,0 @@
Scriptable Event Handler with State<br/>Main Style
==================================================
{{#include ../links.md}}
```admonish example
A runnable example of this implementation is included.
See the [_Examples_]({{rootUrl}}/start/examples/rust.md) section for details.
```
Initialize Handler Instance with `Engine::call_fn_with_options`
---------------------------------------------------------------
Use `Engine::call_fn_with_options` instead of `Engine::call_fn` in order to retain new [variables]
defined inside the custom [`Scope`] when running the `init` function.
```rust
impl Handler {
// Create a new 'Handler'.
pub fn new(path: impl Into<PathBuf>) -> Self {
let mut engine = Engine::new();
:
// Code omitted
:
// Run the 'init' function to initialize the state, retaining variables.
let options = CallFnOptions::new()
.eval_ast(false) // do not re-evaluate the AST
.rewind_scope(false); // do not rewind scope
// In a real application you'd again be handling errors...
engine.call_fn_with_options(options, &mut scope, &ast, "init", ()).unwrap();
:
// Code omitted
:
Self { engine, scope, ast }
}
}
```
Handler Scripting Style
-----------------------
Because the stored state is kept in a custom [`Scope`], it is possible for all [functions] defined
in the handler script to access and modify these state variables.
The API registered with the [`Engine`] can be also used throughout the script.
### Sample script
```js
/// Initialize user-provided state (shadows system-provided state, if any).
/// Because 'CallFnOptions::rewind_scope' is 'false', new variables introduced
/// will remain inside the custom 'Scope'.
fn init() {
// Add 'bool_state' and 'obj_state' as new state variables
let bool_state = false;
let obj_state = new_state(0);
// Constants can also be added!
const EXTRA_CONSTANT = "hello, world!";
}
/// Without 'OOP' support, the can only be a function.
fn log(value, data) {
print(`State = ${value}, data = ${data}`);
}
/// 'start' event handler
fn start(data) {
if bool_state {
throw "Already started!";
}
if obj_state.func1() || obj_state.func2() {
throw "Conditions not yet ready to start!";
}
bool_state = true;
obj_state.value = data;
// Constants 'MY_CONSTANT' and 'EXTRA_CONSTANT'
// in custom scope are also visible!
print(`MY_CONSTANT = ${MY_CONSTANT}`);
print(`EXTRA_CONSTANT = ${EXTRA_CONSTANT}`);
}
/// 'end' event handler
fn end(data) {
if !bool_state {
throw "Not yet started!";
}
if !obj_state.func1() && !obj_state.func2() {
throw "Conditions not yet ready to end!";
}
bool_state = false;
obj_state.value = data;
}
/// 'update' event handler
fn update(data) {
obj_state.value += process(data);
// Without OOP support, can only call function
log(obj_state.value, data);
}
```
Disadvantages of This Style
---------------------------
This style is simple to implement and intuitive to use, but it is not very flexible.
New user state [variables] are introduced by evaluating a special initialization script [function]
(e.g. `init`) that defines them, and `Engine::call_fn_with_scope` is used to keep them inside the
custom [`Scope`] (together with setting `CallFnOptions::rewind_scope` to `false`).
However, this has the disadvantage that no other [function] can introduce new state [variables],
otherwise they'd simply _[shadow]_ existing [variables] in the custom [`Scope`]. Thus, [functions]
are called during events via `Engine::call_fn` which does not retain any [variables].
When there are a large number of state [variables], this style also makes it easy for local
[variables] defined in user [functions] to accidentally _[shadow]_ a state [variable] with a
[variable] that just happens to be the same name.
```rust
// 'start' event handler
fn start(data) {
let bool_state = false; // <- oops! bad variable name!
: // there is now no way to access the
: // state variable 'bool_state'...
if bool_state { // <- 'bool_state' is not the right thing
... // unless this is what you actually want
}
:
:
}
```

View File

@@ -1,190 +0,0 @@
Scriptable Event Handler with State<br/>JS Style
================================================
{{#include ../links.md}}
```admonish example
A runnable example of this implementation is included.
See the [_Examples_]({{rootUrl}}/start/examples/rust.md) section for details.
```
Keep State in Object Map
------------------------
This style allows defining new user state [variables] everywhere by packaging them all inside an
[object map], which is then exposed via the `this` pointer.
Because this scripting style resembles JavaScript, it is so named.
State variables can be freely created by _all_ [functions] (not just the `init` [function]).
The event handler type needs to hold this [object map] instead of a custom [`Scope`].
```rust
use rhai::{Engine, Scope, Dynamic, AST};
// Event handler
struct Handler {
// Scripting engine
pub engine: Engine,
// The custom 'Scope' can be used to hold global constants
pub scope: Scope<'static>,
// Use an object map (as a 'Dynamic') to keep stored state
pub states: Dynamic,
// Program script
pub ast: AST
}
```
Bind Object Map to `this` Pointer
---------------------------------
Initialization can simply be done via binding the [object map] containing global states to the
`this` pointer.
```rust
impl Handler {
// Create a new 'Handler'.
pub fn new(path: impl Into<PathBuf>) -> Self {
let mut engine = Engine::new();
:
// Code omitted
:
// Use an object map to hold state
let mut states = Map::new();
// Default states can be added
states.insert("bool_state".into(), Dynamic::FALSE);
// Convert the object map into 'Dynamic'
let mut states: Dynamic = states.into();
// Use 'call_fn_with_options' instead of 'call_fn' to bind the 'this' pointer
let options = CallFnOptions::new()
.eval_ast(false) // do not re-evaluate the AST
.rewind_scope(true) // rewind scope
.bind_this_ptr(&mut states); // bind the 'this' pointer
// In a real application you'd again be handling errors...
engine.call_fn_with_options(options, &mut scope, &ast, "init", ()).unwrap();
:
// Code omitted
:
Self { engine, scope, states, ast }
}
}
```
Bind `this` Pointer During Events Handling
------------------------------------------
Events handling should also use `Engine::call_fn_with_options` to bind the [object map] containing
global states to the `this` pointer via `CallFnOptions::this_ptr`.
```rust
pub fn on_event(&mut self, event_name: &str, event_data: i64) -> Dynamic {
let engine = &self.engine;
let scope = &mut self.scope;
let states = &mut self.states;
let ast = &self.ast;
let options = CallFnOptions::new()
.eval_ast(false) // do not re-evaluate the AST
.rewind_scope(true) // rewind scope
.bind_this_ptr(&mut states); // bind the 'this' pointer
match event_name {
// In a real application you'd be handling errors...
"start" => engine.call_fn_with_options(options, scope, ast, "start", (event_data,)).unwrap(),
:
:
}
}
```
Handler Scripting Style
-----------------------
```admonish note.side "No shadowing"
Notice that `this` can never be [shadowed][shadowing] because it is not a valid [variable] name.
```
Because the stored state is kept in an [object map], which in turn is bound to `this`, it is
necessary for [functions] to always access or modify these state [variables] via the `this` pointer.
As it is impossible to declare a local [variable] named `this`, there is no risk of accidentally
_[shadowing]_ a state [variable].
Because an [object map] is used to hold state values, it is even possible to add user-defined
[functions], leveraging the [OOP] support for [object maps].
### Sample script
```js
/// Initialize user-provided state.
/// State is stored inside an object map bound to 'this'.
fn init() {
// Can detect system-provided default states!
// Add 'bool_state' as new state variable if one does not exist
if "bool_state" !in this {
this.bool_state = false;
}
// Add 'obj_state' as new state variable (overwrites any existing)
this.obj_state = new_state(0);
// Can also add OOP-style functions!
this.log = |x| print(`State = ${this.obj_state.value}, data = ${x}`);
}
/// 'start' event handler
fn start(data) {
// Access state variables via 'this'
if this.bool_state {
throw "Already started!";
}
// New state variables can be created anywhere
this.start_mode = data;
if this.obj_state.func1() || this.obj_state.func2() {
throw "Conditions not yet ready to start!";
}
this.bool_state = true;
this.obj_state.value = data;
// Constant 'MY_CONSTANT' in custom scope is also visible!
print(`MY_CONSTANT = ${MY_CONSTANT}`);
}
/// 'end' event handler
fn end(data) {
if !this.bool_state || !("start_mode" in this) {
throw "Not yet started!";
}
if !this.obj_state.func1() && !this.obj_state.func2() {
throw "Conditions not yet ready to end!";
}
this.bool_state = false;
this.obj_state.value = data;
}
/// 'update' event handler
fn update(data) {
this.obj_state.value += process(data);
// Call user-defined function OOP-style!
this.log(data);
}
```

View File

@@ -1,163 +0,0 @@
Scriptable Event Handler with State<br/>Map Style
=================================================
{{#include ../links.md}}
```admonish example
A runnable example of this implementation is included.
See the [_Examples_]({{rootUrl}}/start/examples/rust.md) section for details.
```
I Hate `this`! How Can I Get Rid of It?
----------------------------------------
You're using Rust and you don't want people to think you're writing lowly JavaScript?
Taking inspiration from the [_JS Style_](events-2.md), a slight modification of the
[_Main Style_](events-1.md) is to store all states inside an [object map] inside a custom [`Scope`].
Nevertheless, instead of writing `this.variable_name` everywhere to access a state variable
(in the [_JS Style_](events-2.md)), you'd write `state.variable_name` instead.
It is up to you to decide whether this is an improvement!
Handler Initialization
----------------------
```admonish note.side "No shadowing 'state'"
Notice that a [variable definition filter] is used to prevent [shadowing] of the states [object map].
```
Implementation wise, this style follows closely the [_Main Style_](events-1.md), but a single
[object map] is added to the custom [`Scope`] which holds all state values.
Global [constants] can still be added to the custom [`Scope`] as normal and used through the script.
Calls to the `init` [function] no longer need to avoid rewinding the [`Scope`] because state
[variables] are added as properties under the states [object map].
```rust
impl Handler {
// Create a new 'Handler'.
pub fn new(path: impl Into<PathBuf>) -> Self {
let mut engine = Engine::new();
// Forbid shadowing of 'state' variable
engine.on_def_var(|_, info, _| Ok(info.name != "state"));
:
// Code omitted
:
// Use an object map to hold state
let mut states = Map::new();
// Default states can be added
states.insert("bool_state".into(), Dynamic::FALSE);
// Add the main states-holding object map and call it 'state'
scope.push("state", states);
// Just a simple 'call_fn' can do here because we're rewinding the 'Scope'
// In a real application you'd again be handling errors...
engine.call_fn(&mut scope, &ast, "init", ()).unwrap();
:
// Code omitted
:
Self { engine, scope, ast }
}
}
```
Handler Scripting Style
-----------------------
The stored state is kept in an [object map] in the custom [`Scope`].
In this example, that [object map] is named `state`, but it can be any name.
### User-defined functions in state
Because an [object map] is used to hold state values, it is even possible to add user-defined
[functions], leveraging the [OOP] support for [object maps].
However, within these user-defined [functions], the `this` pointer binds to the [object map].
Therefore, the variable-accessing syntax is different from the main body of the script.
```js
fn do_action() {
// Access state: `state.xxx`
state.number = 42;
// Add OOP functions - you still need to use `this`...
state.log = |x| print(`State = ${this.value}, data = ${x}`);
}
```
### Sample script
```js
/// Initialize user-provided state.
/// State is stored inside an object map bound to 'state'.
fn init() {
// Add 'bool_state' as new state variable if one does not exist
if "bool_state" !in state {
state.bool_state = false;
}
// Add 'obj_state' as new state variable (overwrites any existing)
state.obj_state = new_state(0);
// Can also add OOP-style functions!
state.log = |x| print(`State = ${this.obj_state.value}, data = ${x}`);
}
/// 'start' event handler
fn start(data) {
// Can detect system-provided default states!
// Access state variables in 'state'
if state.bool_state {
throw "Already started!";
}
// New values can be added to the state
state.start_mode = data;
if state.obj_state.func1() || state.obj_state.func2() {
throw "Conditions not yet ready to start!";
}
state.bool_state = true;
state.obj_state.value = data;
// Constant 'MY_CONSTANT' in custom scope is also visible!
print(`MY_CONSTANT = ${MY_CONSTANT}`);
}
/// 'end' event handler
fn end(data) {
if !state.bool_state || "start_mode" !in state {
throw "Not yet started!";
}
if !state.obj_state.func1() && !state.obj_state.func2() {
throw "Conditions not yet ready to end!";
}
state.bool_state = false;
state.obj_state.value = data;
}
/// 'update' event handler
fn update(data) {
state.obj_state.value += process(data);
// Call user-defined function OOP-style!
state.log(data);
}
```

View File

@@ -1,232 +0,0 @@
Scriptable Event Handler with State
===================================
{{#include ../links.md}}
```admonish tip "IMPORTANT PATTERN"
In many usage scenarios, a scripting engine is used to provide flexibility in event handling.
That means to execute certain **actions** in response to certain **_events_** that occur at run-time,
and scripts are used to provide flexibility for coding those actions.
You'd be surprised how many applications fit under this pattern &ndash; they are all essentially
event handling systems.
```
```admonish example "Examples"
Because of the importance of this pattern, runnable examples are included.
See the [_Examples_](../start/examples/rust.md) section for details.
```
```admonish info "Usage scenario"
* A system sends _events_ that must be handled.
* Flexibility in event handling must be provided, through user-side scripting.
* State must be kept between invocations of event handlers.
* State may be provided by the system or the user, or both.
* Default implementations of event handlers can be provided.
```
```admonish abstract "Key concepts"
* An _event handler_ object is declared that holds the following items:
* [`Engine`] with registered functions serving as an API,
* [`AST`] of the user script,
* [`Scope`] containing system-provided default state.
* User-provided state is initialized by a [function] called via [`Engine::call_fn_with_options`][`call_fn`].
* Upon an event, the appropriate event handler [function] in the script is called via
[`Engine::call_fn`][`call_fn`].
* Optionally, trap the `EvalAltResult::ErrorFunctionNotFound` error to provide a default implementation.
```
Basic Infrastructure
--------------------
### Declare handler object
In most cases, it would be simpler to store an [`Engine`] instance together with the handler object
because it only requires registering all API functions only once.
In rare cases where handlers are created and destroyed in a tight loop, a new [`Engine`] instance
can be created for each event. See [_One Engine Instance Per Call_](parallel.md) for more details.
```rust
use rhai::{Engine, Scope, AST};
// Event handler
struct Handler {
// Scripting engine
pub engine: Engine,
// Use a custom 'Scope' to keep stored state
pub scope: Scope<'static>,
// Program script
pub ast: AST
}
```
### Register API for custom types
[Custom types] are often used to hold state. The easiest way to register an entire API is via a [plugin module].
```rust
use rhai::plugin::*;
// A custom type to a hold state value.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct TestStruct {
data: i64
}
// Plugin module containing API to TestStruct
#[export_module]
mod test_struct_api {
#[rhai_fn(global)]
pub fn new_state(value: i64) -> TestStruct {
TestStruct { data: value }
}
#[rhai_fn(global)]
pub fn func1(obj: &mut TestStruct) -> bool {
:
}
#[rhai_fn(global)]
pub fn func2(obj: &mut TestStruct) -> i64 {
:
}
pub fn process(data: i64) -> i64 {
:
}
#[rhai_fn(get = "value", pure)]
pub fn get_value(obj: &mut TestStruct) -> i64 {
obj.data
}
#[rhai_fn(set = "value")]
pub fn set_value(obj: &mut TestStruct, value: i64) {
obj.data = value;
}
}
```
### Initialize handler object
Steps to initialize the event handler:
1. Register an API with the [`Engine`],
2. Create a custom [`Scope`] to serve as the stored state,
3. Add default state [variables] into the custom [`Scope`],
4. Optionally, call an initiation [function] to create new state [variables];
`Engine::call_fn_with_options` is used instead of `Engine::call_fn` so that [variables] created
inside the [function] will not be removed from the custom [`Scope`] upon exit,
5. Get the handler script and [compile][`AST`] it,
6. Store the compiled [`AST`] for future evaluations,
7. Run the [`AST`] to initialize event handler state [variables].
```rust
impl Handler {
// Create a new 'Handler'.
pub fn new(path: impl Into<PathBuf>) -> Self {
let mut engine = Engine::new();
// Register custom types and APIs
engine.register_type_with_name::<TestStruct>("TestStruct")
.register_global_module(exported_module!(test_struct_api).into());
// Create a custom 'Scope' to hold state
let mut scope = Scope::new();
// Add any system-provided state into the custom 'Scope'.
// Constants can be used to optimize the script.
scope.push_constant("MY_CONSTANT", 42_i64);
:
:
// Initialize state variables
:
:
// Compile the handler script.
// In a real application you'd be handling errors...
let ast = engine.compile_file_with_scope(&mut scope, path).unwrap();
// The event handler is essentially these three items:
Self { engine, scope, ast }
}
}
```
### Hook up events
There is usually an interface or trait that gets called when an event comes from the system.
Mapping an event from the system into a scripted handler is straight-forward, via `Engine::call_fn`.
```rust
impl Handler {
// Say there are three events: 'start', 'end', 'update'.
// In a real application you'd be handling errors...
pub fn on_event(&mut self, event_name: &str, event_data: i64) -> Dynamic {
let engine = &self.engine;
let scope = &mut self.scope;
let ast = &self.ast;
match event_name {
// The 'start' event maps to function 'start'.
// In a real application you'd be handling errors...
"start" => engine.call_fn(scope, ast, "start", (event_data,)).unwrap(),
// The 'end' event maps to function 'end'.
// In a real application you'd be handling errors...
"end" => engine.call_fn(scope, ast, "end", (event_data,)).unwrap(),
// The 'update' event maps to function 'update'.
// This event provides a default implementation when the scripted function is not found.
"update" =>
engine.call_fn(scope, ast, "update", (event_data,))
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_name, _)
if fn_name.starts_with("update") =>
{
// Default implementation of 'update' event handler.
self.scope.set_value("obj_state", TestStruct::new(42));
// Turn function-not-found into a success.
Ok(Dynamic::UNIT)
}
_ => Err(err)
}).unwrap(),
// In a real application you'd be handling unknown events...
_ => panic!("unknown event: {}", event_name)
}
}
}
```
Scripting Styles
----------------
Depending on needs and scripting style, there are three different ways to implement this pattern.
| | [Main style](events-1.md) | [JS style](events-2.md) | [Map style](events-3.md) |
| ------------------------------------------------- | :---------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | :--------------------------------------------------------------------------: |
| States store | custom [`Scope`] | [object map] bound to `this` | [object map] in custom [`Scope`] |
| Access state [variable] | normal [variable] | [property][object map] of `this` | [property][object map] of state variable |
| Access global [constants]? | yes | yes | yes |
| Add new state [variable]? | `init` [function] only | all [functions] | all [functions] |
| Add new global [constants]? | yes | **no** | **no** |
| [OOP]-style [functions] on states? | **no** | yes | yes |
| Detect system-provided initial states? | **no** | yes | yes |
| Local [variable] may _[shadow]_ state [variable]? | yes | **no** | **no** |
| Benefits | simple | fewer surprises | versatile |
| Disadvantages | <ul><li>no new variables in [functions] (apart from `init`)</li><li>easy variable name collisions</li></ul> | <ul><li>`this.xxx` all over the place</li><li>more complex implementation</li></ul> | <ul><li>`state.xxx` all over the place</li><li>inconsistent syntax</li></ul> |

View File

@@ -1,155 +0,0 @@
Mutable Global State
====================
{{#include ../links.md}}
Don't Do It™
------------
```admonish question.side "Consider JavaScript"
Generations of programmers struggled to get around mutable global state (a.k.a. the `window` object)
in the design of JavaScript.
```
In contrast to [global constants](constants.md), _mutable_ global states are **strongly
discouraged** because:
1) It is a sure-fire way to create race conditions &ndash; that is why Rust does not support it;
2) It adds considerably to debug complexity &ndash; it is difficult to reason, in large code bases,
where/when a state value is being modified;
3) It forces hard (but obscure) dependencies between separate pieces of code that are difficult to
break when the need arises;
4) It is almost impossible to add new layers of redirection and/or abstraction afterwards without
major surgery.
Alternative &ndash; Use `this`
------------------------------
In the majority of the such scenarios, there is only _one_ mutable global state of interest.
Therefore, it is a _much_ better solution to bind that global state to the `this` pointer.
```rust
// Say this is a mutable global state...
let state = #{ counter: 0 };
// This function tries to access the global 'state'
// which will fail.
fn inc() {
state.counter += 1;
}
// The function should be written with 'this'
fn inc() {
this.counter += 1;
}
state.inc(); // call 'inc' with 'state' bound to 'this'
// Or this way... why hard-code the state in the first place?
fn inc() {
this += 1;
}
state.counter.inc();
```
```admonish question.small "Why is this better?"
There are good reasons why using `this` is a better solution:
* the state is never _hidden_ &ndash; it is always clear to see what is being modified
* it is just as fast &ndash; the `this` pointer works by reference
* you can pass other states in, in the future, without changing the script code
* there are no hard links within functions that will be difficult to unravel
* only the [variable] bound to `this` is ever modified; everything else is immutable
```
```admonish danger.small "I don't care! I want it! Just tell me how to do it! Now!"
This is not something that Rhai encourages. _You Have Been Warned™_.
There are two ways...
```
Option 1 &ndash; Get/Set Functions
----------------------------------
This is similar to the [Control Layer](control.md) pattern.
Use get/set functions to read/write the global mutable state.
```rust
// The globally mutable shared value
let value = Rc::new(RefCell::new(42));
// Register an API to access the globally mutable shared value
let v = value.clone();
engine.register_fn("get_global_value", move || *v.borrow());
let v = value.clone();
engine.register_fn("set_global_value", move |value: i64| *v.borrow_mut() = value);
```
These functions can be used in script [functions] to access the shared global state.
```rust
fn foo() {
let current = get_global_value(); // Get global state value
current += 1;
set_global_value(current); // Modify global state value
}
```
This option is preferred because it is possible to modify the get/set functions later on to
add/change functionalities without introducing breaking script changes.
Option 2 &ndash; Variable Resolver
----------------------------------
Declare a [variable resolver] that returns a _shared_ value which is the global state.
```rust
// Use a shared value as the global state
let value: Dynamic = 1.into();
let mut value = value.into_shared(); // convert into shared value
// Clone the shared value
let v = value.clone();
// Register a variable resolver.
engine.on_var(move |name, _, _| {
match name
"value" => Ok(Some(v.clone())),
_ => Ok(None)
}
});
// The shared global state can be modified
*value.write_lock::<i64>().unwrap() = 42;
```
The global state variable can now be used just like a normal local variable,
including modifications.
```rust
fn foo() {
value = value * 2;
// ^ global variable can be read
// ^ global variable can also be modified
}
```
```admonish danger.small "Anti-Pattern"
This option makes mutable global state so easy to implement that it should actually be
considered an _Anti-Pattern_.
```

View File

@@ -1,107 +0,0 @@
Hot Reloading
=============
{{#include ../links.md}}
```admonish info "Usage scenario"
* A system where scripts are used for behavioral control.
* All or parts of the control scripts need to be modified dynamically without re-initializing the
host system.
* New scripts must be active as soon as possible after modifications are detected.
```
```admonish abstract "Key concepts"
* The Rhai [`Engine`] is _re-entrant_, meaning that it is decoupled from scripts.
* A new script only needs to be recompiled and the new [`AST`] replaces the old for new behaviors
to be active.
* Surgically _patch_ scripts when only parts of the scripts are modified.
```
Implementation
--------------
### Embed scripting engine and script into system
Say, a system has a Rhai [`Engine`] plus a compiled script (in [`AST`] form), with the [`AST`] kept
with interior mutability...
```rust
// Main system object
struct System {
engine: Engine,
script: Rc<RefCell<AST>>,
:
}
// Embed Rhai 'Engine' and control script
let engine = Engine::new();
let ast = engine.compile_file("config.rhai")?;
let mut system = System { engine, script: Rc::new(RefCell::new(ast)) };
// Handle events with script functions
system.on_event(|sys: &System, event: &str, data: Map| {
let mut scope = Scope::new();
// Call script function which is the same name as the event
sys.engine.call_fn(&mut scope, sys.script.borrow(), event, (data,)).unwrap();
result
});
```
### Hot reload entire script upon change
If the control scripts are small enough and changes are infrequent, it is much simpler just to
recompile the whole set of script and replace the original [`AST`] with the new one.
```rust
// Watch for script file change
system.watch(|sys: &System, file: &str| {
// Compile the new script
let ast = sys.engine.compile_file(file.into())?;
// Hot reload - just replace the old script!
*sys.script.borrow_mut() = ast;
Ok(())
});
```
### Hot patch specific functions
If the control scripts are large and complicated, and if the system can detect changes to specific [functions],
it is also possible to _patch_ just the changed [functions].
```rust
// Watch for changes in the script
system.watch_for_script_change(|sys: &mut System, fn_name: &str| {
// Get the script file that contains the function
let script = get_script_file_path(fn_name);
// Compile the new script
let mut patch_ast = sys.engine.compile_file(script)?;
// Remove everything other than the specified function
patch_ast.clear_statements();
patch_ast.retain_functions(|_, _, name, _| name == fn_name);
// Hot reload (via +=) only those functions in the script!
*sys.script.borrow_mut() += patch_ast;
});
```
```admonish tip.small "Tip: Multi-threaded considerations"
For a multi-threaded environments, replace `Rc` with `Arc`, `RefCell` with `RwLock` or `Mutex`, and
turn on the [`sync`] feature.
```

View File

@@ -1,119 +0,0 @@
Simulate Macros to Simplify Scripts
===================================
{{#include ../links.md}}
```admonish info "Usage scenario"
* Scripts need to access existing data in [variables].
* The particular fields to access correspond to long/complex expressions (e.g. long
[indexing][indexers] and/or [property][getters/setters] chains `foo[x][y].bar[z].baz`).
* Usage is prevalent inside the scripts, requiring extensive duplications of code that
are prone to typos and errors.
* There are a few such [variables] to modify at the same time &ndash; otherwise, it would
be simpler to bind the `this` pointer to the [variable].
```
```admonish abstract "Key concepts"
* Pick a _macro_ syntax that is unlikely to conflict with content in literal [strings].
* Before script evaluation/compilation, globally replace macros with their corresponding expansions.
```
Pick a Macro Syntax
-------------------
```admonish danger.side "Warning: Not real macros"
The technique described here is to _simulate_ macros.
They are not _REAL_ macros.
```
Pick a syntax that is intuitive for the domain but **unlikely to occur naturally inside
[string] literals**.
| Sample Syntax | Sample usage |
| ------------- | ------------------ |
| `#FOO` | `#FOO = 42;` |
| `$Bar` | `$Bar.work();` |
| `<Baz>` | `print(<Baz>);` |
| `#HELLO#` | `let x = #HELLO#;` |
| `%HEY%` | `%HEY% += 1;` |
~~~admonish tip.small "Tip: Avoid normal words"
Avoid normal syntax that may show up inside a [string] literal.
For example, if using `Target` as a macro:
```rust
// This script...
Target.do_damage(10);
if Target.hp <= 0 {
print("Target is destroyed!");
}
// Will turn to this...
entities["monster"].do_damage(10);
if entities["monster"].hp <= 0 {
// Text in string literal erroneously replaced!
print("entities["monster"] is destroyed!");
}
```
~~~
Global Search/Replace
---------------------
```rust
// Replace macros with expansions
let script = script.replace("#FOO", "foo[x][y].bar[z].baz");
let mut scope = Scope::new();
// Add global variables
scope.push("foo", ...);
scope.push_constant("x", ...);
scope.push_constant("y", ...);
scope.push_constant("z", ...);
// Run the script as normal
engine.run_with_scope(&mut scope, script)?;
```
~~~admonish example.small "Example script"
```js
print(`Found entity FOO at (${x},${y},${z})`);
let speed = #FOO.speed;
if speed < 42 {
#FOO.speed *= 2;
} else {
#FOO.teleport(#FOO.home());
}
print(`FOO is now at (${ #FOO.current_location() })`);
```
~~~
```admonish bug.small "Character positions"
After macro expansion, the _character positions_ of different script
elements will be shifted based on the length of the expanded text.
Therefore, error positions may no longer point to the correct locations
in the original, unexpanded scripts.
Line numbers are not affected.
```

View File

@@ -1,136 +0,0 @@
Multi-Layered Functions
=======================
{{#include ../links.md}}
```admonish info "Usage scenario"
* A system is divided into separate _layers_, each providing logic in terms of scripted [functions].
* A lower layer provides _default implementations_ of certain [functions].
* Higher layers each provide progressively more specific implementations of the same [functions].
* A more specific [function], if defined in a higher layer, always overrides the implementation
in a lower layer.
* This is akin to object-oriented programming but with [functions].
* This type of system is extremely convenient for dynamic business rules configuration, setting
corporate-wide policies, granting permissions for specific roles etc. where specific, local rules
need to override corporate-wide defaults.
```
```admonish tip "Practical scenario"
Assuming a LOB (line-of-business) system for a large MNC (multi-national corporation) with branches,
facilities and offices across the globe.
The system needs to provide basic, corporate-wide policies to be enforced through the worldwide
organization, but also cater for country- or region-specific rules, practices and regulations.
| Layer | Description |
| :---------: | --------------------------------------------------- |
| `corporate` | corporate-wide policies |
| `regional` | regional policy overrides |
| `country` | country-specific modifications for legal compliance |
| `office` | special treatments for individual office locations |
```
```admonish abstract "Key concepts"
* Each layer is a separate script.
* The lowest layer script is compiled into a base [`AST`].
* Higher layer scripts are also compiled into [`AST`] and _combined_ into the base using
[`AST::combine`]({{rootUrl}}/engine/ast.md) (or the `+=` operator), overriding any existing [functions].
```
Examples
--------
Assume the following four scripts, one for each layer:
```rust
┌────────────────┐
│ corporate.rhai │
└────────────────┘
// Default implementation of 'foo'.
fn foo(x) { x + 1 }
// Default implementation of 'bar'.
fn bar(x, y) { x + y }
// Default implementation of 'no_touch'.
fn no_touch() { throw "do not touch me!"; }
┌───────────────┐
│ regional.rhai │
└───────────────┘
// Specific implementation of 'foo'.
fn foo(x) { x * 2 }
// New implementation for this layer.
fn baz() { print("hello!"); }
┌──────────────┐
│ country.rhai │
└──────────────┘
// Specific implementation of 'bar'.
fn bar(x, y) { x - y }
// Specific implementation of 'baz'.
fn baz() { print("hey!"); }
┌─────────────┐
│ office.rhai │
└─────────────┘
// Specific implementation of 'foo'.
fn foo(x) { x + 42 }
```
Load and combine them sequentially:
```rust
let engine = Engine::new();
// Compile the baseline layer.
let mut ast = engine.compile_file("corporate.rhai".into())?;
// Combine the first layer.
let lowest = engine.compile_file("regional.rhai".into())?;
ast += lowest;
// Combine the second layer.
let middle = engine.compile_file("country.rhai".into())?;
ast += middle;
// Combine the third layer.
let highest = engine.compile_file("office.rhai".into())?;
ast += highest;
// Now, 'ast' contains the following functions:
//
// fn no_touch() { // from 'corporate.rhai'
// throw "do not touch me!";
// }
// fn foo(x) { x + 42 } // from 'office.rhai'
// fn bar(x, y) { x - y } // from 'country.rhai'
// fn baz() { print("hey!"); } // from 'country.rhai'
```
```admonish failure.small "No super call"
Unfortunately, there is no `super` call that calls the base implementation (i.e. no way for a
higher-layer function to call an equivalent lower-layer function).
```

View File

@@ -1,116 +0,0 @@
Multi-Threaded Synchronization
==============================
{{#include ../links.md}}
```admonish info "Usage scenarios"
* A system needs to communicate with an [`Engine`] running in a separate thread.
* Multiple [`Engine`]s running in separate threads need to coordinate/synchronize with each other.
```
```admonish abstract "Key concepts"
* An MPSC channel (or any other appropriate synchronization primitive) is used to send/receive
messages to/from an [`Engine`] running in a separate thread.
* An API is registered with the [`Engine`] that is essentially _blocking_ until synchronization is achieved.
```
Example
-------
```rust
use rhai::{Engine};
fn main() {
// Channel: Script -> Master
let (tx_script, rx_master) = std::sync::mpsc::channel();
// Channel: Master -> Script
let (tx_master, rx_script) = std::sync::mpsc::channel();
// Spawn thread with Engine
std::thread::spawn(move || {
// Create Engine
let mut engine = Engine::new();
// Register API
// Notice that the API functions are blocking
engine.register_fn("get", move || rx_script.recv().unwrap())
.register_fn("put", move |v: i64| tx_script.send(v).unwrap());
// Run script
engine.run(
r#"
print("Starting script loop...");
loop {
// The following call blocks until there is data
// in the channel
let x = get();
print(`Script Read: ${x}`);
x += 1;
print(`Script Write: ${x}`);
// The following call blocks until the data
// is successfully sent to the channel
put(x);
}
"#).unwrap();
});
// This is the main processing thread
println!("Starting main loop...");
let mut value = 0_i64;
while value < 10 {
println!("Value: {value}");
// Send value to script
tx_master.send(value).unwrap();
// Receive value from script
value = rx_master.recv().unwrap();
}
}
```
Considerations for [`sync`]
---------------------------
`std::mpsc::Sender` and `std::mpsc::Receiver` are not `Sync`, therefore they cannot be used in
registered functions if the [`sync`] feature is enabled.
In that situation, it is possible to wrap the `Sender` and `Receiver` each in a `Mutex` or `RwLock`,
which makes them `Sync`.
This, however, incurs the additional overhead of locking and unlocking the `Mutex` or `RwLock`
during every function call, which is technically not necessary because there are no other references
to them.
```admonish note "Async"
The example above highlights the fact that Rhai scripts can call any Rust function, including ones
that are _blocking_.
However, Rhai is essentially a blocking, single-threaded engine. Therefore it does _not_ provide an
async API.
That means, although it is simple to use Rhai within a multi-threading environment where blocking a
thread is acceptable or even expected, it is currently not possible to call _async_ functions within
Rhai scripts because there is no mechanism in [`Engine`] to wrap the state of the call stack inside
a future.
Fortunately an [`Engine`] is re-entrant so it can be shared among many async tasks. It is usually
possible to split a script into multiple parts to avoid having to call async functions.
Creating an [`Engine`] is also relatively cheap (extremely cheap if creating a [raw `Engine`]),
so it is also a valid pattern to spawn a new [`Engine`] instance for each task.
```

View File

@@ -1,95 +0,0 @@
Multiple Instantiation
======================
{{#include ../links.md}}
Rhai's [features] are not strictly additive. This is easily deduced from the [`no_std`] feature
which prepares the crate for `no-std` builds. Obviously, turning on this feature has a material
impact on how Rhai behaves.
Many crates resolve this by going the opposite direction: build for `no-std` in default, but add a
`std` feature, included by default, which builds for the `stdlib`.
Rhai, however, is more complex.
Rhai Language Features Are Not Additive
---------------------------------------
Language features cannot be easily made _additive_.
That is because the _lack_ of a language feature is a feature by itself.
```admonish question "A practical illustration"
Assume an _additive_ feature called `floating-point` that adds floating-point support.
Assume also that the application _omits_ the `floating-point` feature (why? perhaps integers are all
that make sense within the project domain). Floating-point numbers do not even parse under this
configuration and will generate syntax errors.
Now, assume that a dependent crate _also_ depends on Rhai, but a new version suddenly decides to
_require_ floating-point support. That dependent crate would, naturally, specify the
`floating-point` feature.
Under such circumstances, unless _exact_ versioning is used and the dependent crate depends on a
_different_ version of Rhai, Cargo automatically _merges_ both dependencies, with the `floating-point`
feature turned on because features are _additive_.
This will in turn break the application, which by itself specifically omits `floating-point` and
expects floating-point numbers to be rejected, in unexpected ways. Suddenly and without warning,
floating-point numbers show up in data which the application is not prepared to handle.
There is no way out of this dilemma, because the _lack_ of a language feature can be depended upon
as a feature.
```
Multiple Instantiations of Rhai Within The Same Project
-------------------------------------------------------
The trick is to differentiate between multiple identical copies of Rhai, each having
a different [features] set, by their _sources_:
* Different versions from [`crates.io`](https://crates.io/crates/rhai/) &ndash; The official crate.
* Different releases from [`GitHub`](https://github.com/rhaiscript/rhai) &ndash; Crate source on GitHub.
* Forked copy of [https://github.com/rhaiscript/rhai](https://github.com/rhaiscript/rhai) on GitHub.
* Local copy of [https://github.com/rhaiscript/rhai](https://github.com/rhaiscript/rhai) downloaded form GitHub.
Use the following configuration in `Cargo.toml` to pull in multiple copies of Rhai within the same project:
```toml
[dependencies]
rhai = { version = "{{version}}", features = [ "no_float" ] }
rhai_github = { git = "https://github.com/rhaiscript/rhai", features = [ "unchecked" ] }
rhai_my_github = { git = "https://github.com/my_github/rhai", branch = "variation1", features = [ "serde", "no_closure" ] }
rhai_local = { path = "../rhai_copy" }
```
The example above creates four different modules: `rhai`, `rhai_github`, `rhai_my_github` and
`rhai_local`, each referring to a different Rhai copy with the appropriate [features] set.
Only one crate of any particular version can be used from each source, because Cargo merges all
candidate cases within the same source, adding all [features] together.
If more than four different instantiations of Rhai is necessary (why?), create more local
repositories or GitHub forks or branches.
```admonish danger.small "No way To avoid dependency conflicts"
Unfortunately, pulling in Rhai from different sources do not resolve the problem of [features]
conflict between dependencies. Even overriding `crates.io` via the `[patch]` manifest section
doesn't work &ndash; all dependencies will eventually find the only one copy.
What is necessary &ndash; multiple copies of Rhai, one for each dependent crate that requires it,
together with their _unique_ [features] set intact. In other words, turning off Cargo's crate
merging feature _just for Rhai_.
Unfortunately, as of this writing, there is no known method to achieve it.
Therefore, moral of the story: avoid pulling in multiple crates that depend on Rhai.
```

View File

@@ -1,84 +0,0 @@
Objects with Defined Behaviors
==============================
{{#include ../links.md}}
```admonish info "Usage scenario"
* To simulate specific _object types_ coded in Rust with fields and methods.
* Native Rust methods (not scripted) are pre-defined for these types.
```
```admonish abstract "Key concepts"
* Use an [object map] as the main container of the type.
* Create [function pointers] binding to _native Rust functions_ and store them as properties
of the [object map].
```
Rhai does not have _objects_ per se and is not object-oriented (in the traditional sense), but it is
possible to _simulate_ object-oriented programming via [object maps].
When using [object maps] to simulate objects (See [here](oop.md) for more details), a property that
holds a [function pointer] can be called like a method function with the [object map] being the
object of the method call.
It is also possible to create [function pointers] that bind to native Rust functions/closures so
that those are called when the [function pointers] are called.
Implementation
--------------
A [function pointer] can be created that binds to a specific Rust function or closure.
(See [here]({{rootUrl}}/language/fn-ptr.md#bind-to-a-native-rust-function) for details on using
`FnPtr::from_fn` and `FnPtr::from_dyn_fn`).
```rust
// This is the pre-defined behavior for the 'Awesome' object type.
fn my_awesome_fn(ctx: NativeCallContext, args: &mut[&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
// Check number of arguments
if args.len() != 2 {
return Err("one argument is required, plus the object".into());
}
// Get call arguments
let x = args[1].try_cast::<i64>().map_err(|_| "argument must be an integer".into())?;
// Get mutable reference to the object map, which is passed as the first argument
let map = &mut *args[0].as_map_mut().map_err(|_| "object must be a map".into())?;
// Do something awesome here ...
let result = ...
Ok(result.into())
}
// Register a function to create a pre-defined object
engine.register_fn("create_awesome_object", || {
// Use an object map as base
let mut map = Map::new();
// Create a function pointer that binds to 'my_awesome_fn'
let fp = FnPtr::from_fn("awesome", my_awesome_fn)?;
// ^ name of method
// ^ native function
// Store the function pointer in the object map
map.insert("awesome".into(), fp.into());
Ok(Dynamic::from_map(map))
});
```
The [object map] can then be used just like any object-oriented object.
```rust
let obj = create_awesome_object();
let result = obj.awesome(42); // calls 'my_awesome_fn' with '42' as argument
```

View File

@@ -1,196 +0,0 @@
Object-Oriented Programming (OOP)
=================================
{{#include ../links.md}}
Rhai does not have _objects_ per se and is not object-oriented (in the traditional sense),
but it is possible to _simulate_ object-oriented programming.
```admonish question.small "To OOP or not to OOP, that is the question."
Regardless of whether object-oriented programming (OOP) should be treated as a pattern or
an _anti-pattern_ (the programming world is split 50-50 on this), there are always users who
would like to write Rhai in "the OOP way."
Rust itself is not object-oriented in the traditional sense; JavaScript also isn't, but that didn't
prevent generations of programmers trying to shoehorn a class-based inheritance system onto it.
So... as soon as Rhai gained in usage, way way before version 1.0, PR's started coming in to make
it possible to write Rhai in "the OOP way."
```
Use Object Maps to Simulate OOP
-------------------------------
Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-maps-oop.md).
| Rhai concept | Maps to OOP |
| ----------------------------------------------------- | :---------: |
| [Object maps] | objects |
| [Object map] properties holding values | properties |
| [Object map] properties that hold [function pointers] | methods |
When a property of an [object map] is called like a method function, and if it happens to hold a
valid [function pointer] (perhaps defined via an [anonymous function] or more commonly as a [closure]),
then the call will be dispatched to the actual function with `this` binding to the
[object map] itself.
Use Closures to Define Methods
------------------------------
[Closures] defined as values for [object map] properties take on a syntactic shape which resembles
very closely that of class methods in an OOP language.
[Closures] also _capture_ [variables] from the defining environment, which is a very common language
feature. It can be turned off via the [`no_closure`] feature.
```rust
let factor = 1;
// Define the object
let obj = #{
data: 0, // object field
increment: |x| this.data += x, // 'this' binds to 'obj'
update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured
action: || print(this.data) // 'this' binds to 'obj'
};
// Use the object
obj.increment(1);
obj.action(); // prints 1
obj.update(42);
obj.action(); // prints 42
factor = 2;
obj.update(42);
obj.action(); // prints 84
```
Simulating Inheritance with Polyfills
-------------------------------------
The `fill_with` method of [object maps] can be conveniently used to _polyfill_ default method
implementations from a _base class_, as per OOP lingo.
Do not use the `mixin` method because it _overwrites_ existing fields.
```rust
// Define base class
let BaseClass = #{
factor: 1,
data: 42,
get_data: || this.data * 2,
update: |x| this.data += x * this.factor
};
let obj = #{
// Override base class field
factor: 100,
// Override base class method
// Notice that the base class can also be accessed, if in scope
get_data: || this.call(BaseClass.get_data) * 999,
}
// Polyfill missing fields/methods
obj.fill_with(BaseClass);
// By this point, 'obj' has the following:
//
// #{
// factor: 100
// data: 42,
// get_data: || this.call(BaseClass.get_data) * 999,
// update: |x| this.data += x * this.factor
// }
// obj.get_data() => (this.data (42) * 2) * 999
obj.get_data() == 83916;
obj.update(1);
obj.data == 142
```
Prototypical Inheritance via Mixin
----------------------------------
Some languages like JavaScript has _prototypical_ inheritance, which bases inheritance on a
_prototype_ object.
It is possible to simulate this form of inheritance using [object maps], leveraging the fact that,
in Rhai, all values are cloned and there are no pointers. This significantly simplifies coding logic.
```rust
// Define prototype 'class'
const PrototypeClass = #{
field: 42,
get_field: || this.field,
set_field: |x| this.field = x
};
// Create instances of the 'class'
let obj1 = PrototypeClass; // a copy of 'PrototypeClass'
obj1.get_field() == 42;
let obj2 = PrototypeClass; // a copy of 'PrototypeClass'
obj2.mixin(#{ // override fields and methods
field: 1,
get_field: || this.field * 2
};
obj2.get_field() == 2;
let obj2 = PrototypeClass + #{ // compact syntax with '+'
field: 1,
get_field: || this.field * 2
};
obj2.get_field() == 2;
// Inheritance chain
const ParentClass = #{
field: 123,
new_field: 0,
action: || print(this.new_field * this.field)
};
const ChildClass = #{
action: || {
this.field = this.new_field;
this.new_field = ();
}
}
let obj3 = PrototypeClass + ParentClass + ChildClass;
// Alternate formulation
const ParentClass = PrototypeClass + #{
field: 123,
new_field: 0,
action: || print(this.new_field * this.field)
};
const ChildClass = ParentClass + #{
action: || {
this.field = this.new_field;
this.new_field = ();
}
}
let obj3 = ChildClass; // a copy of 'ChildClass'
```

View File

@@ -1,86 +0,0 @@
One `Engine` Instance Per Call
==============================
{{#include ../links.md}}
```admonish info "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 closures][`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.
```
```admonish abstract "Key concepts"
* Rhai's [`AST`] structure is sharable &ndash; meaning that one copy of the [`AST`] can be run on
multiple instances of [`Engine`] simultaneously.
* Rhai's [packages] and [modules] are also sharable.
* This means that [`Engine`] instances can be _decoupled_ from the base system ([packages] and
[modules]) as well as the scripts ([`AST`]) so they can be created very cheaply.
```
Procedure
---------
* Gather up all common custom functions into a [custom package].
* This [custom package] should also include standard [packages] needed. For example, to duplicate
`Engine::new`, use a [`StandardPackage`]({{rootUrl}}/rust/packages/builtin.md).
* [Packages] are sharable, so using a [custom package] is _much cheaper_ than registering all the
functions one by one.
* Store a global [`AST`] for use with all [`Engine`] instances.
* 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.
* Register the [custom package] with the [raw `Engine`] via `Package::register_into_engine`.
Examples
--------
```rust
use rhai::def_package;
use rhai::packages::{Package, StandardPackage};
use rhai::FuncRegistration;
// Define the custom package 'MyCustomPackage'.
def_package! {
/// My own personal super-duper custom package
// Aggregate other base packages simply by listing them after the colon.
pub MyCustomPackage(module) : StandardPackage {
// Register additional Rust functions.
FuncRegistration::new("foo")
.with_params(&["s: ImmutableString", "i64"])
.set_into_module(module, |s: ImmutableString| foo(s.into_owned()));
}
}
let ast = /* ... some AST ... */;
let my_custom_package = MyCustomPackage::new();
// The following loop creates 10,000 Engine instances!
for x in 0..10_000 {
// Create a raw Engine - extremely cheap
let mut engine = Engine::new_raw();
// Register custom package - cheap
my_custom_package.register_into_engine(&mut engine);
// Evaluate script
engine.run_ast(&ast)?;
}
```

View File

@@ -1,164 +0,0 @@
Passing External References to Rhai (Unsafe)
============================================
{{#include ../links.md}}
[`transmute`]: https://doc.rust-lang.org/std/mem/fn.transmute.html
[`Engine::set_default_tag`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.set_default_tag
```admonish danger "Don't Do It™"
As with anything `unsafe`, don't do this unless you have exhausted all possible alternatives.
There are usually alternatives.
```
```admonish info "Usage scenario"
* A system where an embedded [`Engine`] is used to call scripts within a callback function/closure
from some external system.
This is extremely common when working with an ECS (Entity Component System) or a GUI library where the script
must draw via the provided graphics context.
* Said external system provides only _references_ (mutable or immutable) to work with their internal states.
* Such states are not available outside of references (and therefore cannot be made shared).
* Said external system's API require such states to function.
* It is impossible to pass references into Rhai because the [`Engine`] does not track lifetimes.
```
```admonish abstract "Key concepts"
* Make a newtype that wraps an integer which is set to the CPU's pointer size (typically `usize`).
* [`transmute`] the reference provided by the system into an integer and store it within the newtype.
This requires `unsafe` code.
* Use the newtype as a _handle_ for all registered API functions, [`transmute`] the integer back
to a reference before use. This also requires `unsafe` code.
```
```admonish bug "Here be dragons..."
Make **absolutely sure** that the newtype is never stored anywhere permanent (e.g. in a [`Scope`])
nor does it ever live outside of the reference's scope!
Otherwise, a crash is the system being nice to you...
```
Example
-------
```rust
/// Newtype wrapping a reference (pointer) cast into 'usize'
/// together with a unique ID for protection.
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct WorldHandle(usize, i64);
/// Create handle from reference
impl From<&mut World> for WorldHandle {
fn from(world: &mut World) -> Self {
Self::new(world)
}
}
/// Recover reference from handle
impl AsMut<World> for WorldHandle {
fn as_mut(&mut self) -> &mut World {
unsafe { std::mem::transmute(self.0) }
}
}
impl WorldHandle {
/// Create handle from reference, using a random number as unique ID
pub fn new(world: &mut World) -> Self {
let handle =unsafe { std::mem::transmute(world) };
let unique_id = rand::random();
Self(handle, unique_id)
}
/// Get the unique ID of this instance
pub fn unique_id(&self) -> i64 {
self.1
}
}
/// API for handle to 'World'
#[export_module]
pub mod handle_module {
pub type Handle = WorldHandle;
/// Draw a bunch of pretty shapes.
#[rhai_fn(return_raw)]
pub fn draw(context: NativeCallContext, handle: &mut Handle, shapes: Array) -> Result<(), Box<EvalAltResult>> {
// Double check the pointer is still fresh
// by comparing the handle's unique ID with
// the version stored in the engine's tag!
if handle.unique_id() != context.tag() {
return "Ouch! The handle is stale!".into();
}
// Get the reference to 'World'
let world: &mut World = handle.as_mut();
// ... work with reference
world.draw_really_pretty_shapes(shapes);
Ok(())
}
}
// Register API for handle type
engine.register_global_module(exported_module!(handle_module).into());
// Successfully pass a reference to 'World' into Rhai
super_ecs_system.query(...).for_each(|world: &mut World| {
// Create handle from reference to 'World'
let handle: WorldHandle = world.into();
// Set the handle's ID into the engine's tag.
// Alternatively, use 'Engine::call_fn_with_options'.
engine.set_default_tag(handle.unique_id());
// Add handle into scope
let mut scope = Scope::new();
scope.push("world", handle);
// Work with handle
engine.run_with_scope(&mut scope, "world.draw([1, 2, 3])")
// Make sure no instance of 'handle' leaks outside this scope!
});
```
```admonish bug "Here be Many Dragons!"
It is of utmost importance that no instance of the handle newtype ever **_leaks_** outside of the
appropriate scope where the wrapped reference may no longer be valid.
For example, do not allow the script to store a copy of the handle anywhere that can
potentially be persistent (e.g. within an [object map]).
#### Safety check via unique ID
One solution, as illustrated in the example, is to always tag each handle instance together with
a unique random ID. That same ID can then be set into the [`Engine`] (via [`Engine::set_default_tag`])
before running scripts.
Before executing any API function, first check whether the handle's ID matches that of the current [`Engine`]
(via [`NativeCallContext::tag`][`NativeCallContext`]). If they differ, the handle is stale and should never be used;
an error should be returned instead.
#### Alternative to `Engine::set_default_tag`
Alternatively, if the [`Engine`] cannot be made mutable, use `Engine::call_fn_with_options`
to set the ID before directly calling a script [function] in a compiled [`AST`].
```

View File

@@ -1,86 +0,0 @@
Serialize an AST
================
{{#include ../links.md}}
In many situations, it is tempting to _serialize_ an [`AST`], so that it can be loaded and recreated
later on.
In Rhai, there is usually little reason to do so.
```admonish failure.small "Don't Do This"
Serialize the [`AST`] into some format for storage.
```
```admonish success.small "Do This"
Store a copy of the original script, preferably compressed.
```
Storing the original script text, preferably compressed (via `gzip` etc.) usually yields much smaller data size.
Plus, is it possibly faster to recompile the original script than to recreate the [`AST`] via
deserialization.
That is because the deserialization processing is essentially a form of parsing, in this case
parsing the serialized data into an [`AST`] &ndash; an equivalent process to what Rhai does, which
is parsing the script text into the same [`AST`].
Illustration
------------
The following script occupies only 42 bytes, possibly less if compressed.
That is only slightly more than 5 words on a 64-bit CPU!
```rust
fn sum(x, y) { x + y }
print(sum(42, 1));
```
The [`AST`] would be _much_ more complicated and looks something like this:
```json
FnDef {
Name: "sum",
ThisType: None,
Parameters: [
"x",
"y"
],
Body: Block [
Return {
Expression {
FnCall {
Name: "+",
Arguments: [
Variable "x",
Variable "y"
]
}
}
}
]
}
Block [
FnCall {
Name: "print",
Arguments: [
Expression {
FnCall {
Name: "sum",
Arguments: [
Constant 42,
Constant 1
]
}
}
]
}
]
```
which would take _much_ more space to serialize.
For instance, the constant 1 alone would take up 8 bytes, while the script text takes up only one byte!

View File

@@ -1,221 +0,0 @@
Singleton Command Object
========================
{{#include ../links.md}}
```admonish info "Usage scenario"
* A system provides core functionalities, but no driving logic.
* The driving logic must be dynamic and hot-loadable.
* A script is used to drive the system and provide control intelligence.
* The API is multiplexed, meaning that it can act on multiple system-provided entities, or
* The API lends itself readily to an object-oriented (OO) representation.
```
```admonish abstract "Key concepts"
* Expose a Command type with an API. The [`no_object`] feature must not be on.
* Leverage [function overloading] to simplify the API design.
* Since Rhai is _[sand-boxed]_, it cannot mutate anything outside of its internal environment.
To perform external actions via an API, the command object type must be wrapped in a `RefCell`
(or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`].
* Load each command object into a custom [`Scope`] as constant variables.
* Control each command object in script via the constants.
```
Implementation
--------------
There are two broad ways for Rhai to control an external system, both of which involve wrapping the
system in a shared, interior-mutated object.
This is the other way which involves directly exposing the data structures of the external system as
a name singleton object in the scripting space.
Use this when the API is complex but has a clear object structure.
For a relatively simple API that is action-based and not object-based, use the
[Control Layer]({{rootUrl}}/patterns/control.md) pattern instead.
### Functional API
Assume the following command object type:
```rust
struct EnergizerBunny { ... }
impl EnergizerBunny {
pub fn new () -> Self { ... }
pub fn go (&mut self) { ... }
pub fn stop (&mut self) { ... }
pub fn is_going (&self) -> bool { ... }
pub fn get_speed (&self) -> i64 { ... }
pub fn set_speed (&mut self, speed: i64) { ... }
pub fn turn (&mut self, left_turn: bool) { ... }
}
```
### Wrap command object type as shared
```rust
pub type SharedBunny = Rc<RefCell<EnergizerBunny>>;
```
or in multi-threaded environments with the [`sync`] feature, use one of the following:
```rust
pub type SharedBunny = Arc<RwLock<EnergizerBunny>>;
pub type SharedBunny = Arc<Mutex<EnergizerBunny>>;
```
### Develop a plugin with methods and getters/setters
The easiest way to develop a complete set of API for a [custom type] is via a [plugin module].
Notice that putting `pure` in `#[rhai_fn(...)]` allows a [getter/setter][getters/setters] to operate
on a [constant] without raising an error. Therefore, it is needed on _all_ functions.
```rust
use rhai::plugin::*;
// Remember to put 'pure' on all functions, or they'll choke on constants!
#[export_module]
pub mod bunny_api {
// Custom type 'SharedBunny' will be called 'EnergizerBunny' in scripts
pub type EnergizerBunny = SharedBunny;
// This constant is also available to scripts
pub const MAX_SPEED: i64 = 100;
#[rhai_fn(get = "power", pure)]
pub fn get_power(bunny: &mut EnergizerBunny) -> bool {
bunny.borrow().is_going()
}
#[rhai_fn(set = "power", pure)]
pub fn set_power(bunny: &mut EnergizerBunny, on: bool) {
if on {
if bunny.borrow().is_going() {
println!("Still going...");
} else {
bunny.borrow_mut().go();
}
} else {
if bunny.borrow().is_going() {
bunny.borrow_mut().stop();
} else {
println!("Already out of battery!");
}
}
}
#[rhai_fn(get = "speed", pure)]
pub fn get_speed(bunny: &mut EnergizerBunny) -> i64 {
if bunny.borrow().is_going() {
bunny.borrow().get_speed()
} else {
0
}
}
#[rhai_fn(set = "speed", pure, return_raw)]
pub fn set_speed(bunny: &mut EnergizerBunny, speed: i64)
-> Result<(), Box<EvalAltResult>>
{
if speed <= 0 {
Err("Speed must be positive!".into())
} else if speed > MAX_SPEED {
Err("Bunny will be going too fast!".into())
} else if !bunny.borrow().is_going() {
Err("Bunny is not yet going!".into())
} else {
b.borrow_mut().set_speed(speed);
Ok(())
}
}
#[rhai_fn(pure)]
pub fn turn_left(bunny: &mut EnergizerBunny) {
if bunny.borrow().is_going() {
bunny.borrow_mut().turn(true);
}
}
#[rhai_fn(pure)]
pub fn turn_right(bunny: &mut EnergizerBunny) {
if bunny.borrow().is_going() {
bunny.borrow_mut().turn(false);
}
}
}
engine.register_global_module(exported_module!(bunny_api).into());
```
```admonish tip.small "Tip: Friendly name for custom type"
It is customary to register a _friendly display name_ for any [custom type]
involved in the plugin.
This can easily be done via a _type alias_ in the plugin module.
```
### Compile script into AST
```rust
let ast = engine.compile(script)?;
```
### Push constant command object into custom scope and run AST
```rust
let bunny: SharedBunny = Rc::new(RefCell::new(EnergizerBunny::new()));
let mut scope = Scope::new();
// Add the singleton command object into a custom Scope.
// Constants, as a convention, are named with all-capital letters.
scope.push_constant("BUNNY", bunny.clone());
// Run the compiled AST
engine.run_ast_with_scope(&mut scope, &ast)?;
// Running the script directly, as below, is less desirable because
// the constant 'BUNNY' will be propagated and copied into each usage
// during the script optimization step
engine.run_with_scope(&mut scope, script)?;
```
~~~admonish tip.small "Tip: Prevent shadowing"
It is usually desirable to prevent [shadowing] of the singleton command object.
This can be easily achieved via a [variable definition filter].
```rust
// Now the script can no longer define a variable named 'BUNNY'
engine.on_def_var(|_, info, _| Ok(info.name != "BUNNY"));
```
~~~
### Use the command API in script
```rust
// Access the command object via constant variable 'BUNNY'.
if !BUNNY.power { BUNNY.power = true; }
if BUNNY.speed > 50 { BUNNY.speed = 50; }
BUNNY.turn_left();
let BUNNY = 42; // <- syntax error if variable definition filter is set
```

View File

@@ -1,104 +0,0 @@
Static Hashing
==============
{{#include ../links.md}}
To counter [DOS] attacks, the _hasher_ used by Rhai, [`ahash`], automatically generates a different
_seed_ for hashing during each compilation and execution run.
This means that hash values generated by the hasher will not be _stable_ &ndash; they change
during each compile, and during each run.
For certain niche scenarios, however, dynamic hashes are undesirable.
So, when _static hashing_ is employed, all hashing uses a fixed, predictable seed all the time.
```admonish abstract.small "Hashing seed"
A hashing seed requires four 64-bit numbers (i.e. `u64`).
```
```admonish tip.small "Tip: Static hashes are consistent and predictable"
Under static hashing, the same function signature _always_ generate the same hash value,
given the same seed.
```
```admonish warning.small "Warning: Safety considerations"
A fixed hashing seed enlarges the attack surface of Rhai to malicious intent
(e.g. [DOS] attacks).
```
Set at Run-Time
---------------
Call the static function `rhai::config::hashing::set_ahash_seed` with four `u64` numbers that make
up the hashing seed.
The seed specified is always used to initialize the hasher.
```rust
// Set specific hashing seed - do this BEFORE anything else!
rhai::config::hashing::set_ahash_seed(Some([123, 456, 789, 42]))?;
// ... from now on, all hashing will be predictable
let engine = Engine::new();
```
```admonish danger.small "Warning: Only call once"
`rhai::config::hashing::set_ahash_seed` can only ever be called _once_,
and must be called _BEFORE_ performing any operation on Rhai (e.g. creating
an [`Engine`]).
Calling it a second time simply returns an error.
```
Set at Compile Time
-------------------
The hashing seed can also be provided, at _compile time_, via the environment variable
`RHAI_AHASH_SEED`, with four `u64` numbers that must be specified in Rust array literal format.
```admonish warning.small "Warning"
If a hashing seed is also set via `rhai::config::hashing::set_ahash_seed`,
this environment variable has no effect.
```
```sh
RHAI_AHASH_SEED="[123, 456, 789, 42]" cargo build ...
```
The seed specified in `RHAI_AHASH_SEED` is always used to initialize the hasher.
If the environment variable is missing, or it contains all zeros (i.e. `[0, 0, 0, 0]`),
then static hashing is disabled.
TL;DR
-----
~~~admonish question "Why can't we tell `ahash` to use a static (fixed) seed?"
For static hashing seed, [`ahash`] requires:
* `default-features = false`
* `runtime-rng` feature is _not_ set (default on)
* `compile-time-rng` feature is _not_ set
#### The bane of additive Cargo features
However, [`ahash`] is also extremely popular, used by many many other crates,
most notably [`hashbrown`](https://crates.io/crates/hashbrown).
Chances are that there are dependency crates that in turn depend on [`ahash`] with
default features. Since cargo features are _additive_, it is almost certain that [`ahash`]
will use a runtime-generated seed for large projects.
Hence, there exists a need to tell [`ahash`] to use a fixed seed, even when its feature flags
say otherwise.
~~~