...
This commit is contained in:
146
rhai_engine/rhaibook/rust/build-type.md
Normal file
146
rhai_engine/rhaibook/rust/build-type.md
Normal file
@@ -0,0 +1,146 @@
|
||||
Register a Custom Type via the Type Builder
|
||||
===========================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
```admonish warning.small "Warning"
|
||||
|
||||
This assumes that the type is defined within the current crate and you can implement traits for it.
|
||||
|
||||
However, you may not _control_ the type (it may be auto-generated or maintained by another user),
|
||||
so you cannot put attributes on it.
|
||||
```
|
||||
|
||||
It is usually convenient to package a [custom type]'s API (i.e. [methods],
|
||||
[properties][getters/setters], [indexers] and [type iterators]) together such that they can be more
|
||||
easily managed.
|
||||
|
||||
This can be achieved by manually implementing the `CustomType` trait, which contains only a single method:
|
||||
|
||||
> ```rust
|
||||
> fn build(builder: TypeBuilder<T>)
|
||||
> ```
|
||||
|
||||
The `TypeBuilder` parameter provides a range of convenient methods to register [methods], property
|
||||
[getters/setters], [indexers] and [type iterators] of a [custom type]:
|
||||
|
||||
| Method | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------- |
|
||||
| `with_name` | set a friendly name |
|
||||
| `on_print` | register the [`to_string`] function that pretty-prints the [custom type] |
|
||||
| `on_debug` | register the [`to_debug`] function that debug-prints the [custom type] |
|
||||
| `with_fn` | register a [method] (or any function really) |
|
||||
| `with_get` | register a property [getter][getters/setters] |
|
||||
| `with_set` | register a property [getter][getters/setters] |
|
||||
| `with_get_set` | register property [getters/setters] |
|
||||
| `with_indexer_get` | register an [indexer] get function |
|
||||
| `with_indexer_set` | register an [indexer] set function |
|
||||
| `with_indexer_get_set` | register [indexer] get/set functions |
|
||||
| `is_iterable` | automatically register a [type iterator] if the [custom type] is iterable |
|
||||
|
||||
```admonish tip.small "Tip: Use plugin module if starting from scratch"
|
||||
|
||||
The `CustomType` trait is typically used on external types that are already defined.
|
||||
|
||||
To define a [custom type] and implement its API from scratch, it is more convenient to use a [plugin module].
|
||||
```
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
// Custom type
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct Vec3 {
|
||||
x: i64,
|
||||
y: i64,
|
||||
z: i64,
|
||||
}
|
||||
|
||||
// Custom type API
|
||||
impl Vec3 {
|
||||
fn new(x: i64, y: i64, z: i64) -> Self {
|
||||
Self { x, y, z }
|
||||
}
|
||||
fn get_x(&mut self) -> i64 {
|
||||
self.x
|
||||
}
|
||||
fn set_x(&mut self, x: i64) {
|
||||
self.x = x
|
||||
}
|
||||
fn get_y(&mut self) -> i64 {
|
||||
self.y
|
||||
}
|
||||
fn set_y(&mut self, y: i64) {
|
||||
self.y = y
|
||||
}
|
||||
fn get_z(&mut self) -> i64 {
|
||||
self.z
|
||||
}
|
||||
fn set_z(&mut self, z: i64) {
|
||||
self.z = z
|
||||
}
|
||||
}
|
||||
|
||||
// The custom type can even be iterated!
|
||||
impl IntoIterator for Vec3 {
|
||||
type Item = i64;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
vec![self.x, self.y, self.z].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
// Use 'CustomType' to register the entire API
|
||||
impl CustomType for Vec3 {
|
||||
fn build(mut builder: TypeBuilder<Self>) {
|
||||
builder
|
||||
.with_name("Vec3")
|
||||
.with_fn("vec3", Self::new)
|
||||
.is_iterable()
|
||||
.with_get_set("x", Self::get_x, Self::set_x)
|
||||
.with_get_set("y", Self::get_y, Self::set_y)
|
||||
.with_get_set("z", Self::get_z, Self::set_z)
|
||||
// Indexer get/set functions that do not panic on invalid indices
|
||||
.with_indexer_get_set(
|
||||
|vec: &mut Self, idx: i64) -> Result<i64, Box<EvalAltResult>> {
|
||||
match idx {
|
||||
0 => Ok(vec.x),
|
||||
1 => Ok(vec.y),
|
||||
2 => Ok(vec.z),
|
||||
_ => Err(EvalAltResult::ErrorIndexNotFound(idx.Into(), Position::NONE).into()),
|
||||
}
|
||||
},
|
||||
|vec: &mut Self, idx: i64, value: i64) -> Result<(), Box<EvalAltResult>> {
|
||||
match idx {
|
||||
0 => vec.x = value,
|
||||
1 => vec.y = value,
|
||||
2 => vec.z = value,
|
||||
_ => Err(EvalAltResult::ErrorIndexNotFound(idx.Into(), Position::NONE).into()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register the custom type in one go!
|
||||
engine.build_type::<Vec3>();
|
||||
```
|
||||
|
||||
~~~admonish question "TL;DR – Why isn't there `is_indexable`?"
|
||||
|
||||
Technically speaking, `TypeBuilder` can automatically register an [indexer] get function if the [custom type] implements `Index`.
|
||||
Similarly, it can automatically register an [indexer] set function for `IndexMut`.
|
||||
|
||||
In practice, however, this is usually not desirable because most `Index`/`IndexMut` implementations panic on invalid indices.
|
||||
|
||||
For Rhai, it is necessary to handle invalid indices properly by returning an error.
|
||||
|
||||
Therefore, in the example above, the `with_indexer_get_set` method properly handles invalid indices by returning errors.
|
||||
~~~
|
139
rhai_engine/rhaibook/rust/collections.md
Normal file
139
rhai_engine/rhaibook/rust/collections.md
Normal file
@@ -0,0 +1,139 @@
|
||||
Custom Collection Types
|
||||
=======================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
~~~admonish tip.side "Tip"
|
||||
|
||||
Collections can also hold [`Dynamic`] values (e.g. like an [array]).
|
||||
~~~
|
||||
|
||||
A _collection_ type holds a... well... _collection_ of items. It can be homogeneous (all items are
|
||||
the same type) or heterogeneous (items are of different types, use [`Dynamic`] to hold).
|
||||
|
||||
Because their only purpose for existence is to hold a number of items, collection types commonly
|
||||
register the following methods.
|
||||
|
||||
<section></section>
|
||||
|
||||
| Method | Description |
|
||||
| ------------------------- | ---------------------------------------------------------------- |
|
||||
| `len` method and property | gets the total number of items in the collection |
|
||||
| `clear` | clears the collection |
|
||||
| `contains` | checks if a particular item exists in the collection |
|
||||
| `add`, `+=` operator | adds a particular item to the collection |
|
||||
| `remove`, `-=` operator | removes a particular item from the collection |
|
||||
| `merge` or `+` operator | merges two collections, yielding a new collection with all items |
|
||||
|
||||
```admonish tip.small "Tip: Define type iterator"
|
||||
|
||||
Collections are typically iterable.
|
||||
|
||||
It is customary to use `Engine::register_iterator` to allow iterating the collection if
|
||||
it implements `IntoIterator`.
|
||||
|
||||
Alternative, register a specific [type iterator] for the [custom type].
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Use a plugin module"
|
||||
|
||||
A [plugin module] makes defining an entire API for a [custom type] a snap.
|
||||
```
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
type MyBag = HashSet<MyItem>;
|
||||
|
||||
engine
|
||||
.register_type_with_name::<MyBag>("MyBag")
|
||||
.register_iterator::<MyBag>()
|
||||
.register_fn("new_bag", || MyBag::new())
|
||||
.register_fn("len", |col: &mut MyBag| col.len() as i64)
|
||||
.register_get("len", |col: &mut MyBag| col.len() as i64)
|
||||
.register_fn("clear", |col: &mut MyBag| col.clear())
|
||||
.register_fn("contains", |col: &mut MyBag, item: i64| col.contains(&item))
|
||||
.register_fn("add", |col: &mut MyBag, item: MyItem| col.insert(item))
|
||||
.register_fn("+=", |col: &mut MyBag, item: MyItem| col.insert(item))
|
||||
.register_fn("remove", |col: &mut MyBag, item: MyItem| col.remove(&item))
|
||||
.register_fn("-=", |col: &mut MyBag, item: MyItem| col.remove(&item))
|
||||
.register_fn("+", |mut col1: MyBag, col2: MyBag| {
|
||||
col1.extend(col2.into_iter());
|
||||
col1
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
What About Indexers?
|
||||
--------------------
|
||||
|
||||
Many users are tempted to register [indexers] for custom collections. This essentially makes the
|
||||
original Rust type something similar to `Vec<MyType>`.
|
||||
|
||||
Rhai's standard [`Array`] type is `Vec<Dynamic>` which already holds an ordered, iterable and
|
||||
indexable collection of dynamic items. Since Rhai has built-in support, manipulating [arrays] is fast.
|
||||
|
||||
In _most_ circumstances, it is better to use [`Array`] instead of a [custom type].
|
||||
|
||||
~~~admonish tip.small "Tip: Convert to `Array` using `.into()`"
|
||||
|
||||
[`Dynamic`] implements `FromIterator` for all iterable types and an [`Array`] is created in the process.
|
||||
|
||||
So, converting a typed array (i.e. `Vec<MyType>`) into an [array] in Rhai is as simple as calling `.into()`.
|
||||
|
||||
```rust
|
||||
// Say you have a custom typed array...
|
||||
let my_custom_array: Vec<MyType> = do_lots_of_calc(42);
|
||||
|
||||
// Convert it into a 'Dynamic' that holds an array
|
||||
let value: Dynamic = my_custom_array.into();
|
||||
|
||||
// Use is anywhere in Rhai...
|
||||
scope.push("my_custom_array", value);
|
||||
|
||||
engine
|
||||
// Raw function that returns a custom type
|
||||
.register_fn("do_lots_of_calc_raw", do_lots_of_calc)
|
||||
// Wrap function that return a custom typed array
|
||||
.register_fn("do_lots_of_calc", |seed: i64| -> Dynamic {
|
||||
let result = do_lots_of_calc(seed); // Vec<MyType>
|
||||
result.into() // Array in Dynamic
|
||||
});
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
TL;DR
|
||||
-----
|
||||
|
||||
~~~admonish question "Why shouldn't we register `Vec<MyType>`?"
|
||||
|
||||
### Reason #1: Performance
|
||||
|
||||
A main reason why anybody would want to do this is to avoid the overhead of storing [`Dynamic`] items.
|
||||
|
||||
This is why [BLOB's] is a built-in data type in Rhai, even though it is actually defined as `Vec<u8>`.
|
||||
The overhead of using [`Dynamic`] (16 bytes) versus `u8` (1 byte) is worth the trouble, although the
|
||||
performance gains may not be as pronounced as expected: benchmarks show a 15% speed improvement inside
|
||||
a tight loop compared with using an [array].
|
||||
|
||||
`Vec<MyType>`, however, will be treated as an opaque [custom type] in Rhai, so performance is not optimized.
|
||||
What you gain from avoiding [`Dynamic`], you pay back in terms of slower access to the `Vec` as well as `MyType`
|
||||
(which is treated as yet another opaque [custom type]).
|
||||
|
||||
### Reason #2: API
|
||||
|
||||
Another reason why it shouldn't be done is due to the large number of functions and methods that must be registered
|
||||
for each type of this sort. One only has to look at the vast API surface of [arrays]({{rootUrl}}/language/arrays.md#built-in-functions)
|
||||
to see the common methods that a user would expect to be available.
|
||||
|
||||
Since `Vec<Type>` looks, feels and quacks just like a normal [array], and the usage syntax is almost equivalent (except
|
||||
for the fact that the data type is restricted), users would be frustrated if they find that certain functions available for
|
||||
[arrays] are not provided.
|
||||
|
||||
This is similar to JavaScript's [_Typed Arrays_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays).
|
||||
They are quite awkward to work with, and basically each has a full API definition that must be pre-registered.
|
||||
~~~
|
||||
|
38
rhai_engine/rhaibook/rust/context-restore.md
Normal file
38
rhai_engine/rhaibook/rust/context-restore.md
Normal file
@@ -0,0 +1,38 @@
|
||||
Advanced Usage – Restore `NativeCallContext`
|
||||
==================================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
The [`NativeCallContext`] type encapsulates the entire _context_ of a script up to the
|
||||
particular point of the native Rust function call.
|
||||
|
||||
The data inside a [`NativeCallContext`] can be stored (as a type `NativeCallContextStore`) for later
|
||||
use, when a new [`NativeCallContext`] can be constructed based on these stored data.
|
||||
|
||||
A reconstructed [`NativeCallContext`] acts almost the same as the original instance, so it is possible
|
||||
to suspend the evaluation of a script, and to continue at a later time with a new
|
||||
[`NativeCallContext`].
|
||||
|
||||
Doing so requires the [`internals`] feature to access internal APIs.
|
||||
|
||||
### Step 1: Store `NativeCallContext` data
|
||||
|
||||
```rust
|
||||
// Store context for later use
|
||||
let context_data = context.store_data();
|
||||
|
||||
// ... store 'context_data' somewhere ...
|
||||
secret_database.push(context_data);
|
||||
```
|
||||
|
||||
### Step 2: Restore `NativeCallContext`
|
||||
|
||||
```rust
|
||||
// ... do something else ...
|
||||
|
||||
// Restore the context
|
||||
let context_data = secret_database.get();
|
||||
|
||||
let new_context = context_data.create_context(&engine);
|
||||
```
|
110
rhai_engine/rhaibook/rust/context.md
Normal file
110
rhai_engine/rhaibook/rust/context.md
Normal file
@@ -0,0 +1,110 @@
|
||||
`NativeCallContext`
|
||||
===================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
If the _first_ parameter of a function is of type `rhai::NativeCallContext`, then it is treated
|
||||
specially by the [`Engine`].
|
||||
|
||||
`NativeCallContext` is a type that encapsulates the current _call context_ of a Rust function call
|
||||
and exposes the following.
|
||||
|
||||
| Method | Return type | Description |
|
||||
| ------------------------- | :----------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `engine()` | [`&Engine`][`Engine`] | the current [`Engine`], with all configurations and settings.<br/>This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. |
|
||||
| `fn_name()` | `&str` | name of the function called (useful when the same Rust function is mapped to multiple Rhai-callable function names) |
|
||||
| `source()` | `Option<&str>` | reference to the current source, if any |
|
||||
| `position()` | `Position` | position of the function call |
|
||||
| `call_level()` | `usize` | the current nesting level of function calls |
|
||||
| `tag()` | [`&Dynamic`][`Dynamic`] | reference to the _custom state_ that is persistent during the current run |
|
||||
| `iter_imports()` | `impl Iterator<Item = (&str,`[`&Module`][`Module`]`)>` | iterator of the current stack of [modules] imported via `import` statements, in reverse order (i.e. later [modules] come first) |
|
||||
| `global_runtime_state()` | [`&GlobalRuntimeState`][`GlobalRuntimeState`] | reference to the current [global runtime state][`GlobalRuntimeState`] (including the stack of [modules] imported via `import` statements); requires the [`internals`] feature |
|
||||
| `iter_namespaces()` | `impl Iterator<Item =`[`&Module`][`Module`]`>` | iterator of the [namespaces][function namespaces] (as [modules]) containing all script-defined [functions], in reverse order (i.e. later [modules] come first) |
|
||||
| `namespaces()` | [`&[&Module]`][`Module`] | reference to the [namespaces][function namespaces] (as [modules]) containing all script-defined [functions]; requires the [`internals`] feature |
|
||||
| `call_fn(...)` | `Result<T, Box<EvalAltResult>>` | call a function with the supplied arguments, casting the result into the required type |
|
||||
| `call_native_fn(...)` | `Result<T, Box<EvalAltResult>>` | call a registered native Rust function with the supplied arguments, casting the result into the required type |
|
||||
| `call_fn_raw(...)` | `Result<`[`Dynamic`]`, Box<EvalAltResult>>` | call a function with the supplied arguments; this is an advanced method |
|
||||
| `call_native_fn_raw(...)` | `Result<`[`Dynamic`]`, Box<EvalAltResult>>` | call a registered native Rust function with the supplied arguments; this is an advanced method |
|
||||
|
||||
|
||||
Example Implementations
|
||||
-----------------------
|
||||
|
||||
~~~admonish example "Example – Implement Safety Checks"
|
||||
|
||||
The native call context is useful for protecting a function from malicious scripts.
|
||||
|
||||
```rust
|
||||
use rhai::{Array, NativeCallContext, EvalAltResult, Position};
|
||||
|
||||
// This function builds an array of arbitrary size, but is protected against attacks
|
||||
// by first checking with the allowed limit set into the 'Engine'.
|
||||
pub fn new_array(context: NativeCallContext, size: i64) -> Result<Array, Box<EvalAltResult>>
|
||||
{
|
||||
let array = Array::new();
|
||||
|
||||
if size <= 0 {
|
||||
return array;
|
||||
}
|
||||
|
||||
let size = size as usize;
|
||||
let max_size = context.engine().max_array_size();
|
||||
|
||||
// Make sure the function does not generate a data structure larger than
|
||||
// the allowed limit for the Engine!
|
||||
if max_size > 0 && size > max_size {
|
||||
return Err(EvalAltResult::ErrorDataTooLarge(
|
||||
"Size to grow".to_string(),
|
||||
max_size, size,
|
||||
context.position(),
|
||||
).into());
|
||||
}
|
||||
|
||||
for x in 0..size {
|
||||
array.push(x.into());
|
||||
}
|
||||
|
||||
OK(array)
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish example "Example – Call a Function Within a Function"
|
||||
|
||||
The _native call context_ can be used to call a [function] within the current evaluation
|
||||
via `call_fn` (or more commonly `call_native_fn`).
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, NativeCallContext};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// A function expecting a callback in form of a function pointer.
|
||||
engine.register_fn("super_call", |context: NativeCallContext, value: i64| {
|
||||
// Call a function within the current evaluation!
|
||||
// 'call_native_fn' ensures that only registered native Rust functions
|
||||
// are called, so a scripted function named 'double' cannot hijack
|
||||
// the process.
|
||||
// To also include scripted functions, use 'call_fn' instead.
|
||||
context.call_native_fn::<i64>("double", (value,))
|
||||
// ^^^^^^^^ arguments passed in tuple
|
||||
});
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish example "Example – Implement a Callback"
|
||||
|
||||
The _native call context_ can be used to call a [function pointer] or [closure] that has been passed
|
||||
as a parameter to the function (via `FnPtr::call_within_context`), thereby implementing a _callback_.
|
||||
|
||||
```rust
|
||||
use rhai::{Dynamic, FnPtr, NativeCallContext, EvalAltResult};
|
||||
|
||||
pub fn greet(context: NativeCallContext, callback: FnPtr) -> Result<String, Box<EvalAltResult>>
|
||||
{
|
||||
// Call the callback closure with the current evaluation context!
|
||||
let name = callback.call_within_context(&context, ())?;
|
||||
Ok(format!("hello, {}!", name))
|
||||
}
|
||||
```
|
||||
~~~
|
97
rhai_engine/rhaibook/rust/custom-types.md
Normal file
97
rhai_engine/rhaibook/rust/custom-types.md
Normal file
@@ -0,0 +1,97 @@
|
||||
Working with Any Rust Type
|
||||
===========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish tip.side "Tip: Shared types"
|
||||
|
||||
The only requirement of a type to work with Rhai is `Clone`.
|
||||
|
||||
Therefore, it is extremely easy to use Rhai with data types such as
|
||||
`Rc<...>`, `Arc<...>`, `Rc<RefCell<...>>`, `Arc<Mutex<...>>` etc.
|
||||
```
|
||||
|
||||
~~~admonish note.side "Under `sync`"
|
||||
|
||||
If the [`sync`] feature is used, a custom type must also be `Send + Sync`.
|
||||
~~~
|
||||
|
||||
Rhai works seamlessly with any Rust type, as long as it implements `Clone` as this allows the
|
||||
[`Engine`] to pass by value.
|
||||
|
||||
A type that is not one of the [standard types] is termed a "custom type".
|
||||
|
||||
Custom types can have the following:
|
||||
|
||||
* a custom (friendly) display name
|
||||
|
||||
* [methods]
|
||||
|
||||
* [property getters and setters](getters/setters)
|
||||
|
||||
* [indexers]
|
||||
|
||||
|
||||
Free Typing
|
||||
-----------
|
||||
|
||||
```admonish question.side "Why \\"Custom\\"?"
|
||||
|
||||
Rhai internally supports a number of standard data types (see [this list][standard types]).
|
||||
|
||||
Any type outside of the list is considered _custom_.
|
||||
```
|
||||
|
||||
```admonish warning.side "Custom types are slower"
|
||||
|
||||
Custom types run _slower_ than [built-in types][standard types] due to an additional
|
||||
level of indirection, but for all other purposes there is no difference.
|
||||
```
|
||||
|
||||
Rhai works seamlessly with _any_ Rust type.
|
||||
|
||||
A custom type is stored in Rhai as a Rust _trait object_ (specifically, a `dyn rhai::Variant`),
|
||||
with no restrictions other than being `Clone` (plus `Send + Sync` under the [`sync`] feature).
|
||||
|
||||
The type literally does not have any prerequisite other than being `Clone`.
|
||||
|
||||
It does not need to implement any other trait or use any custom `#[derive]`.
|
||||
|
||||
This allows Rhai to be integrated into an existing Rust code base with as little plumbing as
|
||||
possible, usually silently and seamlessly.
|
||||
|
||||
External types that are not defined within the same crate (and thus cannot implement special Rhai
|
||||
traits or use special `#[derive]`) can also be used easily with Rhai.
|
||||
|
||||
Support for custom types can be turned off via the [`no_object`] feature.
|
||||
|
||||
|
||||
Register API
|
||||
------------
|
||||
|
||||
For Rhai scripts to interact with the custom type, and API must be registered for it with the [`Engine`].
|
||||
|
||||
The API can consist of functions, [methods], property [getters/setters], [indexers],
|
||||
[iterators][type iterators] etc.
|
||||
|
||||
There are three ways to register an API for a custom type.
|
||||
|
||||
|
||||
### 1. Auto-Generate API
|
||||
|
||||
If you have complete control of the type, then this is the easiest way.
|
||||
|
||||
The [`#[derive(CustomType)]`](derive-custom-type.md) macro can be used to automatically generate an
|
||||
API for a custom type via the [`CustomType`] trait.
|
||||
|
||||
### 2. Custom Type Builder
|
||||
|
||||
For types in the same crate that you do not control, each function, [method], property [getter/setter][getters/setters],
|
||||
[indexer] and [iterator][type iterator] can be registered manually, as a single package, via the [`CustomType`] trait
|
||||
using the _Custom Type Builder_.
|
||||
|
||||
### 3. Manual Registration
|
||||
|
||||
For external types that cannot implement the [`CustomType`] trait due to Rust's [_orphan rule_](https://doc.rust-lang.org/book/ch10-02-traits.html),
|
||||
each function, [method], property [getter/setter][getters/setters], [indexer] and [iterator][type iterator]
|
||||
must be registered manually with the [`Engine`].
|
227
rhai_engine/rhaibook/rust/derive-custom-type.md
Normal file
227
rhai_engine/rhaibook/rust/derive-custom-type.md
Normal file
@@ -0,0 +1,227 @@
|
||||
Auto-Generate API for Custom Type
|
||||
=================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
```admonish warning.small "Warning"
|
||||
|
||||
This assumes that you have complete control of the type and can do whatever you want with it
|
||||
(such as putting attributes on fields).
|
||||
|
||||
In particular, the type must be defined within the current crate.
|
||||
```
|
||||
|
||||
To register a type and its API for use with an [`Engine`], the simplest method is via the [`CustomType`] trait.
|
||||
|
||||
A custom derive macro is provided to auto-implement [`CustomType`] on any `struct` type,
|
||||
which exposes all the type's fields to an [`Engine`] all at once.
|
||||
|
||||
It is as simple as adding `#[derive(CustomType)]` to the type definition.
|
||||
|
||||
```rust
|
||||
use rhai::{CustomType, TypeBuilder}; // <- necessary imports
|
||||
|
||||
#[derive(Clone, CustomType)] // <- auto-implement 'CustomType'
|
||||
pub struct Vec3 { // for normal structs
|
||||
pub x: i64,
|
||||
pub y: i64,
|
||||
pub z: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, CustomType)] // <- auto-implement 'CustomType'
|
||||
pub struct ABC(i64, bool, String); // for tuple structs
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register the custom types!
|
||||
engine.build_type::<Vec3>()
|
||||
.build_type::<ABC>();
|
||||
```
|
||||
|
||||
|
||||
Custom Attribute Options
|
||||
------------------------
|
||||
|
||||
The `rhai_type` attribute, with options, can be added to the fields of the type to customize the auto-generated API.
|
||||
|
||||
| Option | Applies to | Value | Description |
|
||||
| :--------: | :---------: | :---------------: | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `name` | type, field | string expression | use this name instead of the type/field name. |
|
||||
| `skip` | field | _none_ | skip this field; cannot be used with any other attribute. |
|
||||
| `readonly` | field | _none_ | only auto-generate getter, no setter; cannot be used with `set`. |
|
||||
| `get` | field | function path | use this getter function (with `&self`) instead of the auto-generated getter; if `get_mut` is also set, this is ignored. |
|
||||
| `get_mut` | field | function path | use this getter function (with `&mut self`) instead of the auto-generated getter. |
|
||||
| `set` | field | function path | use this setter function instead of the auto-generated setter; cannot be used with `readonly`. |
|
||||
| `extra` | type | function path | call this function after building the type to add additional APIs |
|
||||
|
||||
### Function signatures
|
||||
|
||||
The signature of the function for `get` is:
|
||||
|
||||
> ```rust
|
||||
> Fn(&T) -> V
|
||||
> ```
|
||||
|
||||
The signature of the function for `get_mut` is:
|
||||
|
||||
> ```rust
|
||||
> Fn(&mut T) -> V
|
||||
> ```
|
||||
|
||||
The signature of the function for `set` is:
|
||||
|
||||
> ```rust
|
||||
> Fn(&mut T, V)
|
||||
> ```
|
||||
|
||||
The signature of the function for `extra` is:
|
||||
|
||||
> ```rust
|
||||
> Fn(&mut TypeBuilder<T>)
|
||||
> ```
|
||||
|
||||
### Example
|
||||
|
||||
```rust
|
||||
use rhai::{CustomType, TypeBuilder}; // <- necessary imports
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(CustomType)] // <- auto-implement 'CustomType'
|
||||
pub struct ABC(
|
||||
#[rhai_type(skip)] // <- 'field0' not included
|
||||
i64,
|
||||
|
||||
#[rhai_type(readonly)] // <- only auto getter, no setter for 'field1'
|
||||
i64,
|
||||
|
||||
#[rhai_type(name = "flag")] // <- override property name for 'field2'
|
||||
bool,
|
||||
|
||||
String // <- auto getter/setter for 'field3'
|
||||
);
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(CustomType)] // <- auto-implement 'CustomType'
|
||||
#[rhai_type(name = "MyFoo", extra = Self::build_extra)] // <- call this type 'MyFoo' and use 'build_extra' to add additional APIs
|
||||
pub struct Foo {
|
||||
#[rhai_type(skip)] // <- field not included
|
||||
dummy: i64,
|
||||
|
||||
#[rhai_type(readonly)] // <- only auto getter, no setter for 'bar'
|
||||
bar: i64,
|
||||
|
||||
#[rhai_type(name = "flag")] // <- override property name
|
||||
baz: bool, // <- auto getter/setter for 'baz'
|
||||
|
||||
#[rhai_type(get = Self::qux)] // <- call custom getter (with '&self') for 'qux'
|
||||
qux: char, // <- auto setter for 'qux'
|
||||
|
||||
#[rhai_type(set = Self::set_hello)] // <- call custom setter for 'hello'
|
||||
hello: String // <- auto getter for 'hello'
|
||||
}
|
||||
|
||||
impl Foo {
|
||||
/// Regular field getter function with `&self`
|
||||
pub fn qux(&self) -> char {
|
||||
self.qux
|
||||
}
|
||||
|
||||
/// Special setter implementation for `hello`
|
||||
pub fn set_hello(&mut self, value: String) {
|
||||
self.hello = if self.baz {
|
||||
let mut s = self.hello.clone();
|
||||
s.push_str(&value);
|
||||
for _ in 0..self.bar { s.push('!'); }
|
||||
s
|
||||
} else {
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/// Additional APIs
|
||||
fn build_extra(builder: &mut TypeBuilder<Self>) {
|
||||
// Register constructor function
|
||||
builder.with_fn("new_foo", || Self::default());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, CustomType)]
|
||||
#[rhai_fn(extra = vec3_build_extra)]
|
||||
pub struct Vec3 {
|
||||
#[rhai_type(get = Self::x, set = Self::set_x)]
|
||||
x: i64,
|
||||
#[rhai_type(get = Self::y, set = Self::set_y)]
|
||||
y: i64,
|
||||
#[rhai_type(get = Self::z, set = Self::set_z)]
|
||||
z: i64,
|
||||
}
|
||||
|
||||
impl Vec3 {
|
||||
fn new(x: i64, y: i64, z: i64) -> Self { Self { x, y, z } }
|
||||
fn x(&self) -> i64 { self.x }
|
||||
fn set_x(&mut self, x: i64) { self.x = x }
|
||||
fn y(&self) -> i64 { self.y }
|
||||
fn set_y(&mut self, y: i64) { self.y = y }
|
||||
fn z(&self) -> i64 { self.z }
|
||||
fn set_z(&mut self, z: i64) { self.z = z }
|
||||
}
|
||||
|
||||
fn vec3_build_extra(builder: &mut TypeBuilder<Self>) {
|
||||
// Register constructor function
|
||||
builder.with_fn("Vec3", Self::new);
|
||||
}
|
||||
```
|
||||
|
||||
~~~admonish question "TL;DR – The above is equivalent to this..."
|
||||
|
||||
```rust
|
||||
impl CustomType for ABC {
|
||||
fn build(mut builder: TypeBuilder<Self>)
|
||||
{
|
||||
builder.with_name("ABC");
|
||||
builder.with_get("field1", |obj: &mut Self| obj.1.clone());
|
||||
builder.with_get_set("flag",
|
||||
|obj: &mut Self| obj.2.clone(),
|
||||
|obj: &mut Self, val| obj.2 = val
|
||||
);
|
||||
builder.with_get_set("field3",
|
||||
|obj: &mut Self| obj.3.clone(),
|
||||
|obj: &mut Self, val| obj.3 = val
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomType for Foo {
|
||||
fn build(mut builder: TypeBuilder<Self>)
|
||||
{
|
||||
builder.with_name("MyFoo");
|
||||
builder.with_get("bar", |obj: &mut Self| obj.bar.clone());
|
||||
builder.with_get_set("flag",
|
||||
|obj: &mut Self| obj.baz.clone(),
|
||||
|obj: &mut Self, val| obj.baz = val
|
||||
);
|
||||
builder.with_get_set("qux",
|
||||
|obj: &Self| Self::qux(&*obj)),
|
||||
|obj: &mut Self, val| obj.qux = val
|
||||
)
|
||||
builder.with_get_set("hello",
|
||||
|obj: &mut Self| obj.hello.clone(),
|
||||
Self::set_hello
|
||||
);
|
||||
Self::build_extra(&mut builder);
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomType for Vec3 {
|
||||
fn build(mut builder: TypeBuilder<Self>)
|
||||
{
|
||||
builder.with_name("Vec3");
|
||||
builder.with_get_set("x", |obj: &mut Self| Self::x(&*obj), Self::set_x);
|
||||
builder.with_get_set("y", |obj: &mut Self| Self::y(&*obj), Self::set_y);
|
||||
builder.with_get_set("z", |obj: &mut Self| Self::z(&*obj), Self::set_z);
|
||||
vec3_build_extra(&mut builder);
|
||||
}
|
||||
}
|
||||
```
|
||||
~~~
|
12
rhai_engine/rhaibook/rust/disable-custom.md
Normal file
12
rhai_engine/rhaibook/rust/disable-custom.md
Normal file
@@ -0,0 +1,12 @@
|
||||
Disable Custom Types
|
||||
====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The [`no_object`] feature disables support for [custom types] including:
|
||||
|
||||
* [_method-style_]({{rootUrl}}/rust/methods.md}}) function calls (e.g. `obj.method()`),
|
||||
|
||||
* [object maps] and the [`Map`] type,
|
||||
|
||||
* the `register_get`, `register_set` and `register_get_set` APIs for [`Engine`]
|
193
rhai_engine/rhaibook/rust/dynamic-args.md
Normal file
193
rhai_engine/rhaibook/rust/dynamic-args.md
Normal file
@@ -0,0 +1,193 @@
|
||||
`Dynamic` Parameters in Rust Functions
|
||||
======================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
It is possible for Rust functions to contain parameters of type [`Dynamic`].
|
||||
|
||||
A [`Dynamic`] value can hold any clonable type.
|
||||
|
||||
```admonish question.small "Trivia"
|
||||
|
||||
The `push` method of an [array] is implemented as follows (minus code for [safety] protection
|
||||
against [over-sized arrays][maximum size of arrays]), allowing the function to be called with
|
||||
all item types.
|
||||
|
||||
~~~rust
|
||||
// 'item: Dynamic' matches all data types
|
||||
fn push(array: &mut Array, item: Dynamic) {
|
||||
array.push(item);
|
||||
}
|
||||
~~~
|
||||
```
|
||||
|
||||
|
||||
Precedence
|
||||
----------
|
||||
|
||||
Any parameter in a registered Rust function with a specific type has higher precedence over
|
||||
[`Dynamic`], so it is important to understand which _version_ of a function will be used.
|
||||
|
||||
Parameter matching starts from the left to the right.
|
||||
Candidate functions will be matched in order of parameter types.
|
||||
|
||||
Therefore, always leave [`Dynamic`] parameters (up to 16, see below) as far to the right as possible.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Dynamic};
|
||||
|
||||
// Different versions of the same function 'foo'
|
||||
// will be matched in the following order.
|
||||
|
||||
fn foo1(x: i64, y: &str, z: bool) { }
|
||||
|
||||
fn foo2(x: i64, y: &str, z: Dynamic) { }
|
||||
|
||||
fn foo3(x: i64, y: Dynamic, z: bool) { }
|
||||
|
||||
fn foo4(x: i64, y: Dynamic, z: Dynamic) { }
|
||||
|
||||
fn foo5(x: Dynamic, y: &str, z: bool) { }
|
||||
|
||||
fn foo6(x: Dynamic, y: &str, z: Dynamic) { }
|
||||
|
||||
fn foo7(x: Dynamic, y: Dynamic, z: bool) { }
|
||||
|
||||
fn foo8(x: Dynamic, y: Dynamic, z: Dynamic) { }
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register all functions under the same name (order does not matter)
|
||||
|
||||
engine.register_fn("foo", foo5)
|
||||
.register_fn("foo", foo7)
|
||||
.register_fn("foo", foo2)
|
||||
.register_fn("foo", foo8)
|
||||
.register_fn("foo", foo1)
|
||||
.register_fn("foo", foo3)
|
||||
.register_fn("foo", foo6)
|
||||
.register_fn("foo", foo4);
|
||||
```
|
||||
|
||||
|
||||
~~~admonish warning "Only the right-most 16 parameters can be `Dynamic`"
|
||||
|
||||
The number of parameter permutations goes up exponentially, and therefore there is a realistic limit
|
||||
of 16 parameters allowed to be [`Dynamic`], counting from the _right-most side_.
|
||||
|
||||
For example, Rhai will not find the following function – Oh! and those 16 parameters to the right
|
||||
certainly have nothing to do with it!
|
||||
|
||||
```rust
|
||||
// The 'd' parameter counts 17th from the right!
|
||||
fn weird(a: i64, d: Dynamic, x1: i64, x2: i64, x3: i64, x4: i64,
|
||||
x5: i64, x6: i64, x7: i64, x8: i64,
|
||||
x9: i64, x10: i64, x11: i64, x12: i64,
|
||||
x13: i64, x14: i64, x15: i64, x16: i64) {
|
||||
|
||||
// ... do something unspeakably evil with all those parameters ...
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
TL;DR
|
||||
-----
|
||||
|
||||
```admonish question "How is this implemented?"
|
||||
|
||||
#### Hash lookup
|
||||
|
||||
Since functions in Rhai can be [overloaded][function overloading], Rhai uses a single _hash_ number
|
||||
to quickly lookup the actual function, based on argument types.
|
||||
|
||||
For each function call, a hash is calculated from:
|
||||
|
||||
1. the function's [namespace][function namespace], if any,
|
||||
2. the function's name,
|
||||
3. number of arguments (its _arity_),
|
||||
4. unique ID of the type of each argument, if any.
|
||||
|
||||
The correct function is then obtained via a simple hash lookup.
|
||||
|
||||
#### Limitations
|
||||
|
||||
This method is _fast_, but at the expense of flexibility (such as multiple argument types that must
|
||||
map to a single version). That is because each type has a different ID, and thus they calculate to
|
||||
different hash numbers.
|
||||
|
||||
This is the reason why [generic functions](generic.md) must be expanded into concrete types.
|
||||
|
||||
The type ID of [`Dynamic`] is different from any other type, but it must match all types seamlessly.
|
||||
Needless to say, this creates a slight problem.
|
||||
|
||||
#### Trying combinations
|
||||
|
||||
If the combined hash calculated from the actual argument type ID's is not found, then the [`Engine`]
|
||||
calculates hashes for different _combinations_ of argument types and [`Dynamic`], systematically
|
||||
replacing different arguments with [`Dynamic`] _starting from the right-most parameter_.
|
||||
|
||||
Thus, assuming a three-argument function call:
|
||||
|
||||
~~~rust
|
||||
foo(42, "hello", true);
|
||||
~~~
|
||||
|
||||
The following hashes will be calculated, in order.
|
||||
They will be _all different_.
|
||||
|
||||
| Order | Hash calculation method |
|
||||
| :---: | --------------------------------------------------- |
|
||||
| 1 | `foo` + 3 + `i64` + `string` + `bool` |
|
||||
| 2 | `foo` + 3 + `i64` + `string` + [`Dynamic`] |
|
||||
| 3 | `foo` + 3 + `i64` + [`Dynamic`] + `bool` |
|
||||
| 4 | `foo` + 3 + `i64` + [`Dynamic`] + [`Dynamic`] |
|
||||
| 5 | `foo` + 3 + [`Dynamic`] + `string` + `bool` |
|
||||
| 6 | `foo` + 3 + [`Dynamic`] + `string` + [`Dynamic`] |
|
||||
| 7 | `foo` + 3 + [`Dynamic`] + [`Dynamic`] + `bool` |
|
||||
| 8 | `foo` + 3 + [`Dynamic`] + [`Dynamic`] + [`Dynamic`] |
|
||||
|
||||
Therefore, the version with all the correct parameter types will always be found first if it exists.
|
||||
|
||||
At soon as a hash is found, the process stops.
|
||||
|
||||
Otherwise, it goes on for up to 16 arguments, or at most 65,536 tries.
|
||||
That's where the 16 parameters limit comes from.
|
||||
```
|
||||
|
||||
```admonish question "What?! It calculates 65,536 hashes for each function call???!!!"
|
||||
|
||||
Of course not. Don't be silly.
|
||||
|
||||
#### Not every function has 16 parameters
|
||||
|
||||
Studies have repeatedly shown that most functions accept few parameters, with the mean between
|
||||
2-3 parameters per function. Functions with more than 5 parameters are rare in normal code bases.
|
||||
If at all, they are usually [closures] that _capture_ lots of external variables, bumping up the
|
||||
parameter count; but [closures] are always script-defined and thus all parameters are already
|
||||
[`Dynamic`].
|
||||
|
||||
In fact, you have a bigger problem if you write such a function that you need to call regularly.
|
||||
It would be far more efficient to group those parameters into [object maps].
|
||||
|
||||
#### Caching to the rescue
|
||||
|
||||
Function hashes are _cached_, so this process only happens _once_, and only up to the number of
|
||||
rounds for the correct function to be found.
|
||||
|
||||
If not, then yes, it will calculate up to 2<sup>_n_</sup> hashes where _n_ is the number of
|
||||
arguments (up to 16). But again, this will only be done _once_ for that particular
|
||||
combination of argument types.
|
||||
```
|
||||
|
||||
```admonish danger "But then... beware module functions"
|
||||
|
||||
The functions resolution _cache_ resides only in the [global namespace][function namespace].
|
||||
This is a limitation.
|
||||
|
||||
Therefore, calls to functions in an [`import`]ed [module] (i.e. _qualified_ with
|
||||
a [namespace][function namespace] path) do not have the benefit of a cache.
|
||||
|
||||
Thus, up to 2<sup>_n_</sup> hashes are calculated during _every_ function call.
|
||||
This is unlikely to cause a performance issue since most functions accept only a few parameters.
|
||||
```
|
94
rhai_engine/rhaibook/rust/dynamic-return.md
Normal file
94
rhai_engine/rhaibook/rust/dynamic-return.md
Normal file
@@ -0,0 +1,94 @@
|
||||
`Dynamic` Return Value
|
||||
======================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai supports registering functions that return [`Dynamic`].
|
||||
|
||||
A [`Dynamic`] value can hold any clonable type.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Dynamic};
|
||||
|
||||
// The 'Dynamic' return type allows this function to
|
||||
// return values of any supported type!
|
||||
fn get_info(bag: &mut PropertyBag, key: &str) -> Dynamic {
|
||||
if let Some(prop_type) = bag.get_type(key) {
|
||||
match prop_type {
|
||||
// Use '.into()' for standard types
|
||||
"string" => bag.get::<&str>(key).into(),
|
||||
"int" => bag.get::<i64>(key).into(),
|
||||
"bool" => bag.get::<bool>(key).into(),
|
||||
:
|
||||
:
|
||||
// Use 'Dynamic::from' for custom types
|
||||
"bag" => Dynamic::from(bag.get::<PropertyBag>(key))
|
||||
}
|
||||
} else {
|
||||
// Return () upon error
|
||||
Dynamic::UNIT
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_fn("get_info", get_info);
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: Create a `Dynamic`"
|
||||
|
||||
To create a [`Dynamic`] value, use `Dynamic::from`.
|
||||
|
||||
[Standard types] in Rhai can also use `.into()`.
|
||||
|
||||
```rust
|
||||
use rhai::Dynamic;
|
||||
|
||||
let obj = TestStruct::new();
|
||||
|
||||
let x = Dynamic::from(obj);
|
||||
|
||||
// '.into()' works for standard types
|
||||
|
||||
let x = 42_i64.into();
|
||||
|
||||
let y = "hello!".into();
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
~~~admonish tip.small "Tip: Alternative to fallible functions"
|
||||
|
||||
Instead of registering a [fallible function], it is usually more idiomatic to leverage the _dynamic_
|
||||
nature of Rhai and simply return [`()`] upon error.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Dynamic};
|
||||
|
||||
// Function that may fail - return () upon failure
|
||||
fn safe_divide(x: i64, y: i64) -> Dynamic {
|
||||
if y == 0 {
|
||||
// Return () to indicate an error if y is zero
|
||||
Dynamic::UNIT
|
||||
} else {
|
||||
// Use '.into()' to convert standard types to 'Dynamic'
|
||||
(x / y).into()
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_fn("divide", safe_divide);
|
||||
|
||||
// The following prints 'error!'
|
||||
engine.run(r#"
|
||||
let result = divide(40, 0);
|
||||
|
||||
if result == () {
|
||||
print("error!");
|
||||
} else {
|
||||
print(result);
|
||||
}
|
||||
"#)?;
|
||||
```
|
||||
~~~
|
54
rhai_engine/rhaibook/rust/fallible.md
Normal file
54
rhai_engine/rhaibook/rust/fallible.md
Normal file
@@ -0,0 +1,54 @@
|
||||
Register a Fallible Rust Function
|
||||
=================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
~~~admonish tip.side.wide "Tip: Consider `Dynamic`"
|
||||
|
||||
A lot of times it is not necessary to register fallible functions.
|
||||
|
||||
Simply have the function returns [`Dynamic`].
|
||||
Upon error, return [`()`] which is idiomatic in Rhai.
|
||||
|
||||
See [here](dynamic-return.md) for more details.
|
||||
~~~
|
||||
|
||||
If a function is _fallible_ (i.e. it returns a `Result<_, _>`), it can also be registered with via
|
||||
`Engine::register_fn`.
|
||||
|
||||
The function must return `Result<T, Box<EvalAltResult>>` where `T` is any clonable type.
|
||||
|
||||
In other words, the error type _must_ be `Box<EvalAltResult>`. It is `Box`ed in order to reduce
|
||||
the size of the `Result` type since the error path is rarely hit.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
// Function that may fail - the error type must be 'Box<EvalAltResult>'
|
||||
fn safe_divide(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> {
|
||||
if y == 0 {
|
||||
// Return an error if y is zero
|
||||
Err("Division by zero!".into()) // shortcut to create Box<EvalAltResult::ErrorRuntime>
|
||||
} else {
|
||||
Ok(x / y)
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_fn("divide", safe_divide);
|
||||
|
||||
if let Err(error) = engine.eval::<i64>("divide(40, 0)") {
|
||||
println!("Error: {error:?}"); // prints ErrorRuntime("Division by zero detected!", (1, 1)")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
~~~admonish tip.small "Tip: Create a `Box<EvalAltResult>`"
|
||||
|
||||
`Box<EvalAltResult>` implements `From<&str>` and `From<String>` etc.
|
||||
and the error text gets converted into `Box<EvalAltResult::ErrorRuntime>`.
|
||||
|
||||
The error values are `Box`-ed in order to reduce memory footprint of the error path,
|
||||
which should be hit rarely.
|
||||
~~~
|
20
rhai_engine/rhaibook/rust/functions-metadata.md
Normal file
20
rhai_engine/rhaibook/rust/functions-metadata.md
Normal file
@@ -0,0 +1,20 @@
|
||||
Get Scripted Functions Metadata from AST
|
||||
=========================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Use [`AST::iter_functions`](https://docs.rs/rhai/latest/rhai/struct.AST.html#method.iter_functions)
|
||||
to iterate through all the script-defined [functions] in an [`AST`].
|
||||
|
||||
|
||||
`ScriptFnMetadata`
|
||||
------------------
|
||||
|
||||
The type returned from the iterator is `ScriptFnMetadata` with the following fields:
|
||||
|
||||
| Field | Requires | Type | Description |
|
||||
| ---------- | :----------: | :---------: | --------------------------------------------------------------------- |
|
||||
| `name` | | `&str` | Name of [function] |
|
||||
| `params` | | `Vec<&str>` | Number of parameters |
|
||||
| `access` | | `FnAccess` | • `FnAccess::Public` (public)<br/>• `FnAccess::Private` ([`private`]) |
|
||||
| `comments` | [`metadata`] | `Vec<&str>` | [Doc-comments], if any, one per line |
|
151
rhai_engine/rhaibook/rust/functions.md
Normal file
151
rhai_engine/rhaibook/rust/functions.md
Normal file
@@ -0,0 +1,151 @@
|
||||
Register a Rust Function for Use in Rhai Scripts
|
||||
================================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai's scripting engine is very lightweight. It gets most of its abilities from functions.
|
||||
|
||||
To call these functions, they need to be _registered_ via `Engine::register_fn`.
|
||||
|
||||
```rust
|
||||
use rhai::{Dynamic, Engine, ImmutableString};
|
||||
|
||||
// Normal function that returns a standard type
|
||||
// Remember to use 'ImmutableString' and not 'String'
|
||||
fn add_len(x: i64, s: ImmutableString) -> i64 {
|
||||
x + s.len()
|
||||
}
|
||||
// Alternatively, '&str' maps directly to 'ImmutableString'
|
||||
fn add_len_count(x: i64, s: &str, c: i64) -> i64 {
|
||||
x + s.len() * c
|
||||
}
|
||||
// Function that returns a 'Dynamic' value
|
||||
fn get_any_value() -> Dynamic {
|
||||
42_i64.into() // standard types can use '.into()'
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Notice that all three functions are overloaded into the same name with
|
||||
// different number of parameters and/or parameter types.
|
||||
engine.register_fn("add", add_len)
|
||||
.register_fn("add", add_len_count)
|
||||
.register_fn("add", get_any_value)
|
||||
.register_fn("inc", |x: i64| { // closure is also OK!
|
||||
x + 1
|
||||
})
|
||||
.register_fn("log", |label: &str, x: i64| {
|
||||
println!("{label} = {x}");
|
||||
});
|
||||
|
||||
let result = engine.eval::<i64>(r#"add(40, "xx")"#)?;
|
||||
|
||||
println!("Answer: {result}"); // prints 42
|
||||
|
||||
let result = engine.eval::<i64>(r#"add(40, "x", 2)"#)?;
|
||||
|
||||
println!("Answer: {result}"); // prints 42
|
||||
|
||||
let result = engine.eval::<i64>("add()")?;
|
||||
|
||||
println!("Answer: {result}"); // prints 42
|
||||
|
||||
let result = engine.eval::<i64>("inc(41)")?;
|
||||
|
||||
println!("Answer: {result}"); // prints 42
|
||||
|
||||
engine.run(r#"log("value", 42)"#)?; // prints "value = 42"
|
||||
```
|
||||
|
||||
~~~admonish tip "Tip: Use closures"
|
||||
|
||||
It is common for short functions to be registered via a _closure_.
|
||||
|
||||
```rust
|
||||
┌──────┐
|
||||
│ Rust │
|
||||
└──────┘
|
||||
|
||||
engine.register_fn("foo", |x: i64, y: i64| x * 2 + y * 3);
|
||||
// ^^^ ^^^
|
||||
// Usually parameter types need to be specified
|
||||
|
||||
engine.register_fn("bar", |x: i64| -> Result<_, Box<EvalAltResult>> { x * 2 });
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// For fallible closures, the return ERROR type may need to be specified
|
||||
|
||||
┌─────────────┐
|
||||
│ Rhai script │
|
||||
└─────────────┘
|
||||
|
||||
foo(42, 100); // <- 42 * 2 + 100 * 3
|
||||
```
|
||||
#### Interact with external environment
|
||||
|
||||
An additional benefit to using closures is that they can capture external variables.
|
||||
|
||||
For example, capturing a type wrapped in shared mutability (e.g. `Rc<RefCell<T>>`)
|
||||
allows a script to interact with the external environment through that shared type.
|
||||
|
||||
See also: [Control Layer]({{rootUrl}}/patterns/control.md).
|
||||
|
||||
```rust
|
||||
┌──────┐
|
||||
│ Rust │
|
||||
└──────┘
|
||||
|
||||
/// A type that encapsulates some behavior.
|
||||
#[derive(Clone)]
|
||||
struct TestStruct { ... }
|
||||
|
||||
impl TestSTruct {
|
||||
/// Some action defined on that type.
|
||||
pub fn do_foo(&self, x: i64, y: bool) {
|
||||
// ... do something drastic with x and y
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapped in shared mutability: Rc<RefCell<TestStruct>>.
|
||||
let shared_obj = Rc::new(RefCell::new(TestStruct::new()));
|
||||
|
||||
/// Clone the shared reference and move it into the closure.
|
||||
let embedded_obj = shared.clone();
|
||||
|
||||
engine.register_fn("foo", move |x: i64, y: bool| {
|
||||
// ^^^^ 'embedded_obj' is captured into the closure
|
||||
|
||||
embedded_obj.borrow().do_foo(x, y);
|
||||
});
|
||||
|
||||
┌─────────────┐
|
||||
│ Rhai script │
|
||||
└─────────────┘
|
||||
|
||||
foo(42, true); // <- equivalent to: shared_obj.borrow().do_foo(42, true);
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish warning "Warning: Don't touch those generic parameters"
|
||||
|
||||
Rhai uses an intricate system of traits (in particular `RhaiNativeFunc`) with many generic parameters to ensure
|
||||
an intuitive and smooth developer experience that _just works_.
|
||||
|
||||
Because of this, it is not recommended to touch those generic parameters directly. These generic parameters may change
|
||||
liberally in future versions of Rhai. In most situations they are automatically inferred by the compiler.
|
||||
|
||||
In the cases where the compiler fail to infer types when registering a _closure_, (usually with the _error_ type
|
||||
of a [fallible function]), manually declare the parameter and/or return types.
|
||||
|
||||
```rust
|
||||
// The following fails to compile because the compiler does not know
|
||||
// the return _error_ type of the closure.
|
||||
// It knows the return type, which is 'Result<i64, E>', but 'E' is not known.
|
||||
engine.register_fn("foo", |x: i64| Ok(x));
|
||||
|
||||
// Don't do this...
|
||||
engine.register_fn::<_, 1, false, i64, Box<EvalAltResult>>("foo", |x: i64| Ok(x));
|
||||
|
||||
// Do this...
|
||||
engine.register_fn("foo", |x: i64| -> Result<i64, Box<EvalAltResult>> { Ok(x) });
|
||||
```
|
||||
~~~
|
36
rhai_engine/rhaibook/rust/generic.md
Normal file
36
rhai_engine/rhaibook/rust/generic.md
Normal file
@@ -0,0 +1,36 @@
|
||||
Register a Generic Rust Function
|
||||
================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish warning.small "No monomorphization"
|
||||
|
||||
Due to its dynamic nature, Rhai cannot monomorphize generic functions automatically.
|
||||
|
||||
Monomorphization of generic functions must be performed manually.
|
||||
```
|
||||
|
||||
Rust generic functions can be used in Rhai, but separate instances for each concrete type must be
|
||||
registered separately.
|
||||
|
||||
This essentially _overloads_ the function with different parameter types as Rhai does not natively
|
||||
support generics but Rhai does support _[function overloading]_.
|
||||
|
||||
The example below shows how to register multiple functions (or, in this case, multiple overloaded
|
||||
versions of the same function) under the same name.
|
||||
|
||||
```rust
|
||||
use std::fmt::Display;
|
||||
|
||||
use rhai::Engine;
|
||||
|
||||
fn show_it<T: Display>(x: &mut T) {
|
||||
println!("put up a good show: {x}!");
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_fn("print", show_it::<i64>)
|
||||
.register_fn("print", show_it::<bool>)
|
||||
.register_fn("print", show_it::<ImmutableString>);
|
||||
```
|
168
rhai_engine/rhaibook/rust/getters-setters.md
Normal file
168
rhai_engine/rhaibook/rust/getters-setters.md
Normal file
@@ -0,0 +1,168 @@
|
||||
Custom Type Property Getters and Setters
|
||||
========================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish warning.side.wide "Cannot override object maps"
|
||||
|
||||
Property getters and setters are intended for [custom types].
|
||||
|
||||
Any getter or setter function registered for [object maps] is simply _ignored_.
|
||||
|
||||
Get/set syntax on [object maps] is interpreted as access to properties.
|
||||
```
|
||||
|
||||
A [custom type] can also expose properties by registering `get` and/or `set` functions.
|
||||
|
||||
Properties can be accessed in a Rust-like syntax:
|
||||
|
||||
> _object_ `.` _property_
|
||||
>
|
||||
> _object_ `.` _property_ `=` _value_ `;`
|
||||
|
||||
The [_Elvis operator_](https://en.wikipedia.org/wiki/Elvis_operator) can be used to short-circuit
|
||||
processing if the object itself is [`()`]:
|
||||
|
||||
> `// returns () if object is ()`
|
||||
> _object_ `?.` _property_
|
||||
>
|
||||
> `// no action if object is ()`
|
||||
> _object_ `?.` _property_ `=` _value_ `;`
|
||||
|
||||
Property getter and setter functions are called behind the scene.
|
||||
They each take a `&mut` reference to the first parameter.
|
||||
|
||||
Getters and setters are disabled under the [`no_object`] feature.
|
||||
|
||||
<section></section>
|
||||
|
||||
| `Engine` API | Function signature(s)<br/>(`T: Clone` = custom type,<br/>`V: Clone` = data type) | Can mutate `T`? |
|
||||
| ------------------ | -------------------------------------------------------------------------------- | :----------------------------: |
|
||||
| `register_get` | `Fn(&mut T) -> V` | yes, but not advised |
|
||||
| `register_set` | `Fn(&mut T, V)` | yes |
|
||||
| `register_get_set` | getter: `Fn(&mut T) -> V`</br>setter: `Fn(&mut T, V)` | yes, but not advised in getter |
|
||||
|
||||
```admonish danger.small "No support for references"
|
||||
|
||||
Rhai does NOT support normal references (i.e. `&T`) as parameters.
|
||||
All references must be mutable (i.e. `&mut T`).
|
||||
```
|
||||
|
||||
```admonish warning.small "Getters must be pure"
|
||||
|
||||
By convention, property getters are assumed to be _pure_, meaning that they are not supposed to
|
||||
mutate the [custom type], although there is nothing that prevents this mutation in Rust.
|
||||
|
||||
Even though a property getter function also takes `&mut` as the first parameter, Rhai assumes that
|
||||
no data is changed when the function is called.
|
||||
```
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct {
|
||||
field: String
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
// Remember &mut must be used even for getters.
|
||||
fn get_field(&mut self) -> String {
|
||||
// Property getters are assumed to be PURE, meaning they are
|
||||
// not supposed to mutate any data.
|
||||
self.field.clone()
|
||||
}
|
||||
|
||||
fn set_field(&mut self, new_val: String) {
|
||||
self.field = new_val;
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self { field: "hello, world!".to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>()
|
||||
.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field)
|
||||
.register_fn("new_ts", TestStruct::new);
|
||||
|
||||
let result = engine.eval::<String>(
|
||||
r#"
|
||||
let a = new_ts();
|
||||
a.xyz = "42";
|
||||
a.xyz
|
||||
"#)?;
|
||||
|
||||
println!("Answer: {result}"); // prints 42
|
||||
```
|
||||
|
||||
|
||||
Fallback to Indexer
|
||||
-------------------
|
||||
|
||||
```admonish note.side "See also"
|
||||
|
||||
See [this section](indexer-prop-fallback.md) for details on an [indexer]
|
||||
acting as fallback to properties.
|
||||
```
|
||||
|
||||
If the getter/setter of a particular property is not defined, but an [indexer] is defined on the
|
||||
[custom type] with [string] index, then the corresponding [indexer] will be called with the name of
|
||||
the property as the index value.
|
||||
|
||||
In other words, [indexers] act as a _fallback_ to property getters/setters.
|
||||
|
||||
```rust
|
||||
a.foo // if property getter for 'foo' doesn't exist...
|
||||
|
||||
a["foo"] // an indexer (if any) is tried
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Implement a property bag"
|
||||
|
||||
This feature makes it very easy for [custom types] to act as _property bags_
|
||||
(similar to an [object map]) which can add/remove properties at will.
|
||||
```
|
||||
|
||||
|
||||
Chaining Updates
|
||||
----------------
|
||||
|
||||
It is possible to _chain_ property accesses and/or indexing (via [indexers]) together to modify a
|
||||
particular property value at the end of the chain.
|
||||
|
||||
Rhai detects such modifications and updates the changed values all the way back up the chain.
|
||||
|
||||
In the end, the syntax works as expected by intuition, automatically and without special attention.
|
||||
|
||||
```rust
|
||||
// Assume a deeply-nested object...
|
||||
let root = get_new_container_object();
|
||||
|
||||
root.prop1.sub["hello"].list[0].value = 42;
|
||||
|
||||
// The above is equivalent to:
|
||||
|
||||
// First getting all the intermediate values...
|
||||
let prop1_value = root.prop1; // via property getter
|
||||
let sub_value = prop1_value.sub; // via property getter
|
||||
let sub_value_item = sub_value["hello"]; // via index getter
|
||||
let list_value = sub_value_item.list; // via property getter
|
||||
let list_item = list_value[0]; // via index getter
|
||||
|
||||
list_item.value = 42; // modify property value deep down the chain
|
||||
|
||||
// Propagate the changes back up the chain...
|
||||
list_value[0] = list_item; // via index setter
|
||||
sub_value_item.list = list_value; // via property setter
|
||||
sub_value["hello"] = sub_value_item; // via index setter
|
||||
prop1_value.sub = sub_value; // via property setter
|
||||
root.prop1 = prop1_value; // via property setter
|
||||
|
||||
// The below prints 42...
|
||||
print(root.prop1.sub["hello"].list[0].value);
|
||||
```
|
76
rhai_engine/rhaibook/rust/immutable-string.md
Normal file
76
rhai_engine/rhaibook/rust/immutable-string.md
Normal file
@@ -0,0 +1,76 @@
|
||||
The `ImmutableString` Type
|
||||
==========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
~~~admonish question.side "Why `SmartString`?"
|
||||
|
||||
[`SmartString`] is used because many strings in scripts are short (fewer than 24 ASCII characters).
|
||||
~~~
|
||||
|
||||
All [strings] in Rhai are implemented as `ImmutableString`, which is an alias to
|
||||
`Rc<SmartString>` (or `Arc<SmartString>` under the [`sync`] feature).
|
||||
|
||||
An `ImmutableString` is immutable (i.e. never changes) and therefore can be shared among many users.
|
||||
Cloning an `ImmutableString` is cheap since it only copies an immutable reference.
|
||||
|
||||
Modifying an `ImmutableString` causes it first to be cloned, and then the modification made to the copy.
|
||||
Therefore, direct string modifications are expensive.
|
||||
|
||||
|
||||
Avoid `String` Parameters
|
||||
-------------------------
|
||||
|
||||
`ImmutableString` should be used in place of `String` for function parameters because using `String`
|
||||
is very inefficient (the argument is cloned during every function call).
|
||||
|
||||
A alternative is to use `&str` which de-sugars to `ImmutableString`.
|
||||
|
||||
A function with the first parameter being `&mut String` does not match a string argument passed to it,
|
||||
which has type `ImmutableString`. In fact, `&mut String` is treated as an opaque [custom type].
|
||||
|
||||
```rust
|
||||
fn slow(s: String) -> i64 { ... } // string is cloned each call
|
||||
|
||||
fn fast1(s: ImmutableString) -> i64 { ... } // cloning 'ImmutableString' is cheap
|
||||
|
||||
fn fast2(s: &str) -> i64 { ... } // de-sugars to above
|
||||
|
||||
fn bad(s: &mut String) { ... } // '&mut String' will not match string values
|
||||
```
|
||||
|
||||
|
||||
Differences from Rust Strings
|
||||
-----------------------------
|
||||
|
||||
Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!),
|
||||
but nevertheless there are major differences.
|
||||
|
||||
In Rhai a [string] is semantically the same as an array of Unicode characters and can be directly
|
||||
indexed (unlike Rust).
|
||||
|
||||
This is similar to most other languages (e.g. JavaScript, C#) where strings are stored internally
|
||||
not as UTF-8 but as arrays of UCS-16 or UCS-32.
|
||||
|
||||
Individual characters within a Rhai string can also be replaced just as if the string is an array of
|
||||
Unicode characters.
|
||||
|
||||
In Rhai, there are also no separate concepts of `String` and `&str` (a string slice) as in Rust.
|
||||
|
||||
|
||||
```admonish warning "Performance considerations"
|
||||
|
||||
Although Rhai exposes a [string] as a simple array of [characters] which can be directly indexed to
|
||||
get at a particular [character], such convenient syntax is an _illusion_.
|
||||
|
||||
Internally the [string] is still stored in UTF-8 (native Rust `String`s).
|
||||
|
||||
All indexing operations actually require walking through the entire UTF-8 string to find the offset
|
||||
of the particular [character] position, and therefore is _much_ slower than the simple array
|
||||
indexing for other scripting languages.
|
||||
|
||||
This implementation detail is hidden from the user but has a performance implication.
|
||||
|
||||
Avoid large scale [character]-based processing of [strings]; instead, build an actual [array] of
|
||||
[characters] (via the `split()` method) which can then be manipulated efficiently.
|
||||
```
|
11
rhai_engine/rhaibook/rust/index.md
Normal file
11
rhai_engine/rhaibook/rust/index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
Extend Rhai with Rust
|
||||
=====================
|
||||
|
||||
{{#title Extend Rhai with Rust}}
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Most features and functionalities required by a Rhai script should actually be coded in Rust,
|
||||
which leverages the superior native run-time speed.
|
||||
|
||||
This section discusses how to extend Rhai with functionalities written in Rust.
|
101
rhai_engine/rhaibook/rust/indexer-prop-fallback.md
Normal file
101
rhai_engine/rhaibook/rust/indexer-prop-fallback.md
Normal file
@@ -0,0 +1,101 @@
|
||||
Indexer as Property Access Fallback
|
||||
===================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish tip.side "Tip: Property bag"
|
||||
|
||||
Such an [indexer] allows easy creation of _property bags_ (similar to [object maps])
|
||||
which can dynamically add/remove properties.
|
||||
```
|
||||
|
||||
An [indexer] taking a [string] index is a special case – it acts as a _fallback_ to property
|
||||
[getters/setters].
|
||||
|
||||
During a property access, if the appropriate property [getter/setter][getters/setters] is not
|
||||
defined, an [indexer] is called and passed the string name of the property.
|
||||
|
||||
This is also extremely useful as a short-hand for [indexers], when the [string] keys conform to
|
||||
property name syntax.
|
||||
|
||||
```rust
|
||||
// Assume 'obj' has an indexer defined with string parameters...
|
||||
|
||||
// Let's create a new key...
|
||||
obj.hello_world = 42;
|
||||
|
||||
// The above is equivalent to this:
|
||||
obj["hello_world"] = 42;
|
||||
|
||||
// You can write this...
|
||||
let x = obj["hello_world"];
|
||||
|
||||
// but it is easier with this...
|
||||
let x = obj.hello_world;
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: Swizzling"
|
||||
|
||||
Since an [indexer] can serve as a _fallback_ to [property access][getters/setters],
|
||||
it is possible to implement [swizzling](https://en.wikipedia.org/wiki/Swizzling_(computer_graphics))
|
||||
of properties for use with vector-like [custom types].
|
||||
|
||||
Such an [indexer] defined on a [custom type] (for instance, `Float4`) can inspect the property name,
|
||||
construct a proper return value based on the swizzle pattern, and return it.
|
||||
|
||||
```rust
|
||||
// Assume 'v' is a 'Float4'
|
||||
let r = v.w; // -> v.w
|
||||
let r = v.xx; // -> Float2::new(v.x, v.x)
|
||||
let r = v.yxz; // -> Float3::new(v.y, v.x, v.z)
|
||||
let r = v.xxzw; // -> Float4::new(v.x, v.x, v.z, v.w)
|
||||
let r = v.yyzzxx; // error: property 'yyzzxx' not found
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Caveat – Reverse is NOT True
|
||||
----------------------------------
|
||||
|
||||
The reverse, however, is not true – when an [indexer] fails or doesn't exist, the corresponding
|
||||
property [getter/setter][getters/setters], if any, is not called.
|
||||
|
||||
```rust
|
||||
type MyType = HashMap<String, i64>;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Define custom type, property getter and string indexers
|
||||
engine.register_type::<MyType>()
|
||||
.register_fn("new_ts", || {
|
||||
let mut obj = MyType::new();
|
||||
obj.insert("foo".to_string(), 1);
|
||||
obj.insert("bar".to_string(), 42);
|
||||
obj.insert("baz".to_string(), 123);
|
||||
obj
|
||||
})
|
||||
// Property 'hello'
|
||||
.register_get("hello", |obj: &mut MyType| obj.len() as i64)
|
||||
// Index getter/setter
|
||||
.register_indexer_get(|obj: &mut MyType, prop: &str| -> Result<i64, Box<EvalAltResult>>
|
||||
obj.get(index).cloned().ok_or_else(|| "not found".into())
|
||||
).register_indexer_set(|obj: &mut MyType, prop: &str, value: i64|
|
||||
obj.insert(prop.to_string(), value)
|
||||
);
|
||||
|
||||
engine.run("let ts = new_ts(); print(ts.foo);");
|
||||
// ^^^^^^
|
||||
// Calls ts["foo"] - getter for 'foo' does not exist
|
||||
|
||||
engine.run("let ts = new_ts(); print(ts.bar);");
|
||||
// ^^^^^^
|
||||
// Calls ts["bar"] - getter for 'bar' does not exist
|
||||
|
||||
engine.run("let ts = new_ts(); ts.baz = 999;");
|
||||
// ^^^^^^^^^^^^
|
||||
// Calls ts["baz"] = 999 - setter for 'baz' does not exist
|
||||
|
||||
engine.run(r#"let ts = new_ts(); print(ts["hello"]);"#);
|
||||
// ^^^^^^^^^^^
|
||||
// Error: Property getter for 'hello' not a fallback for indexer
|
||||
```
|
197
rhai_engine/rhaibook/rust/indexers.md
Normal file
197
rhai_engine/rhaibook/rust/indexers.md
Normal file
@@ -0,0 +1,197 @@
|
||||
Custom Type Indexers
|
||||
====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
A [custom type] can also expose an _indexer_ by registering an indexer function.
|
||||
|
||||
A [custom type] with an indexer function defined can use the bracket notation to get/set a property
|
||||
value at a particular index:
|
||||
|
||||
> _object_ `[` _index_ `]`
|
||||
>
|
||||
> _object_ `[` _index_ `]` `=` _value_ `;`
|
||||
|
||||
The [_Elvis notation_][elvis] is similar except that it returns [`()`] if the object itself is [`()`].
|
||||
|
||||
> `// returns () if object is ()`
|
||||
> _object_ `?[` _index_ `]`
|
||||
>
|
||||
> `// no action if object is ()`
|
||||
> _object_ `?[` _index_ `]` `=` _value_ `;`
|
||||
|
||||
Like property [getters/setters], indexers take a `&mut` reference to the first parameter.
|
||||
|
||||
They also take an additional parameter of any type that serves as the _index_ within brackets.
|
||||
|
||||
Indexers are disabled when the [`no_index`] and [`no_object`] features are used together.
|
||||
|
||||
| `Engine` API | Function signature(s)<br/>(`T: Clone` = custom type,<br/>`X: Clone` = index type,<br/>`V: Clone` = data type) | Can mutate `T`? |
|
||||
| -------------------------- | ------------------------------------------------------------------------------------------------------------- | :----------------------------: |
|
||||
| `register_indexer_get` | `Fn(&mut T, X) -> V` | yes, but not advised |
|
||||
| `register_indexer_set` | `Fn(&mut T, X, V)` | yes |
|
||||
| `register_indexer_get_set` | getter: `Fn(&mut T, X) -> V`<br/>setter: `Fn(&mut T, X, V)` | yes, but not advised in getter |
|
||||
|
||||
```admonish danger.small "No support for references"
|
||||
|
||||
Rhai does NOT support normal references (i.e. `&T`) as parameters.
|
||||
All references must be mutable (i.e. `&mut T`).
|
||||
```
|
||||
|
||||
```admonish warning.small "Getters must be pure"
|
||||
|
||||
By convention, index getters are not supposed to mutate the [custom type],
|
||||
although there is nothing that prevents this mutation.
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: `EvalAltResult::ErrorIndexNotFound`"
|
||||
|
||||
For [fallible][fallible function] indexers, it is customary to return
|
||||
`EvalAltResult::ErrorIndexNotFound` when called with an invalid index value.
|
||||
~~~
|
||||
|
||||
|
||||
Cannot Override Arrays, BLOB's, Object Maps, Strings and Integers
|
||||
-----------------------------------------------------------------
|
||||
|
||||
```admonish failure.side "Plugins"
|
||||
|
||||
They can be defined in a [plugin module], but will be ignored.
|
||||
```
|
||||
|
||||
For efficiency reasons, indexers **cannot** be used to overload (i.e. override)
|
||||
built-in indexing operations for [arrays], [object maps], [strings] and integers
|
||||
(acting as [bit-field] operation).
|
||||
|
||||
The following types have built-in indexer implementations that are fast and efficient.
|
||||
|
||||
<section></section>
|
||||
|
||||
| Type | Index type | Return type | Description |
|
||||
| ----------------------------------------- | :---------------------------------------: | :---------: | ---------------------------------------------------------------------------- |
|
||||
| [`Array`] | `INT` | [`Dynamic`] | access a particular element inside the [array] |
|
||||
| [`Blob`] | `INT` | `INT` | access a particular byte value inside the [BLOB] |
|
||||
| [`Map`] | [`ImmutableString`],<br/>`String`, `&str` | [`Dynamic`] | access a particular property inside the [object map] |
|
||||
| [`ImmutableString`],<br/>`String`, `&str` | `INT` | [character] | access a particular [character] inside the [string] |
|
||||
| `INT` | `INT` | boolean | access a particular bit inside the integer number as a [bit-field] |
|
||||
| `INT` | [range] | `INT` | access a particular range of bits inside the integer number as a [bit-field] |
|
||||
|
||||
```admonish warning.small "Do not overload indexers for built-in standard types"
|
||||
|
||||
In general, it is a bad idea to overload indexers for any of the [standard types] supported
|
||||
internally by Rhai, since built-in indexers may be added in future versions.
|
||||
```
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct {
|
||||
fields: Vec<i64>
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
// Remember &mut must be used even for getters
|
||||
fn get_field(&mut self, index: String) -> i64 {
|
||||
self.fields[index.len()]
|
||||
}
|
||||
fn set_field(&mut self, index: String, value: i64) {
|
||||
self.fields[index.len()] = value
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self { fields: vec![1, 2, 3, 4, 5] }
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>()
|
||||
.register_fn("new_ts", TestStruct::new)
|
||||
// Short-hand: .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
|
||||
.register_indexer_get(TestStruct::get_field)
|
||||
.register_indexer_set(TestStruct::set_field);
|
||||
|
||||
let result = engine.eval::<i64>(
|
||||
r#"
|
||||
let a = new_ts();
|
||||
a["xyz"] = 42; // these indexers use strings
|
||||
a["xyz"] // as the index type
|
||||
"#)?;
|
||||
|
||||
println!("Answer: {result}"); // prints 42
|
||||
```
|
||||
|
||||
|
||||
Convention for Negative Index
|
||||
-----------------------------
|
||||
|
||||
If the indexer takes a signed integer as an index (e.g. the standard `INT` type), care should be
|
||||
taken to handle _negative_ values passed as the index.
|
||||
|
||||
It is a standard API _convention_ for Rhai to assume that an index position counts _backwards_ from
|
||||
the _end_ if it is negative.
|
||||
|
||||
`-1` as an index usually refers to the _last_ item, `-2` the second to last item, and so on.
|
||||
|
||||
Therefore, negative index values go from `-1` (last item) to `-length` (first item).
|
||||
|
||||
A typical implementation for negative index values is:
|
||||
|
||||
```rust
|
||||
// The following assumes:
|
||||
// 'index' is 'INT', 'items: usize' is the number of elements
|
||||
let actual_index = if index < 0 {
|
||||
index.checked_abs().map_or(0, |n| items - (n as usize).min(items))
|
||||
} else {
|
||||
index as usize
|
||||
};
|
||||
```
|
||||
|
||||
The _end_ of a data type can be interpreted creatively. For example, in an integer used as a
|
||||
[bit-field], the _start_ is the _least-significant-bit_ (LSB) while the `end` is the
|
||||
_most-significant-bit_ (MSB).
|
||||
|
||||
|
||||
Convention for Range Index
|
||||
--------------------------
|
||||
|
||||
```admonish tip.side.wide "Tip: Negative values"
|
||||
|
||||
By convention, negative values are _not_ interpreted specially in indexers for [ranges].
|
||||
```
|
||||
|
||||
It is very common for [ranges] to be used as indexer parameters via the types
|
||||
`std::ops::Range<INT>` (exclusive) and `std::ops::RangeInclusive<INT>` (inclusive).
|
||||
|
||||
One complication is that two versions of the same indexer must be defined to support _exclusive_
|
||||
and _inclusive_ [ranges] respectively.
|
||||
|
||||
```rust
|
||||
use std::ops::{Range, RangeInclusive};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
/// Version of indexer that accepts an exclusive range
|
||||
.register_indexer_get_set(
|
||||
|obj: &mut TestStruct, range: Range<i64>| -> bool { ... },
|
||||
|obj: &mut TestStruct, range: Range<i64>, value: bool| { ... },
|
||||
)
|
||||
/// Version of indexer that accepts an inclusive range
|
||||
.register_indexer_get_set(
|
||||
|obj: &mut TestStruct, range: RangeInclusive<i64>| -> bool { ... },
|
||||
|obj: &mut TestStruct, range: RangeInclusive<i64>, value: bool| { ... },
|
||||
);
|
||||
|
||||
engine.run(
|
||||
"
|
||||
let obj = new_ts();
|
||||
|
||||
let x = obj[0..12]; // use exclusive range
|
||||
|
||||
obj[0..=11] = !x; // use inclusive range
|
||||
")?;
|
||||
```
|
180
rhai_engine/rhaibook/rust/methods-fn-call.md
Normal file
180
rhai_engine/rhaibook/rust/methods-fn-call.md
Normal file
@@ -0,0 +1,180 @@
|
||||
Call Method as Function
|
||||
=======================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Method-Call Style vs. Function-Call Style
|
||||
-----------------------------------------
|
||||
|
||||
### Method-call syntax
|
||||
|
||||
> _object_ `.` _function_ `(` _parameter_`,` ... `,` _parameter_`)`
|
||||
|
||||
~~~admonish warning.small "_Method-call_ style not supported under [`no_object`]"
|
||||
```rust
|
||||
// Below is a syntax error under 'no_object'.
|
||||
engine.run("let x = [42]; x.clear();")?;
|
||||
// ^ cannot call method-style
|
||||
```
|
||||
~~~
|
||||
|
||||
### Function-call syntax
|
||||
|
||||
> _function_ `(` _object_`,` _parameter_`,` ... `,` _parameter_`)`
|
||||
|
||||
### Equivalence
|
||||
|
||||
```admonish note.side
|
||||
|
||||
This design is similar to Rust.
|
||||
```
|
||||
|
||||
Internally, methods on a [custom type] is _the same_ as a function taking a `&mut` first argument of
|
||||
the object's type.
|
||||
|
||||
Therefore, methods and functions can be called interchangeably.
|
||||
|
||||
```rust
|
||||
impl TestStruct {
|
||||
fn foo(&mut self) -> i64 {
|
||||
self.field
|
||||
}
|
||||
}
|
||||
|
||||
engine.register_fn("foo", TestStruct::foo);
|
||||
|
||||
let result = engine.eval::<i64>(
|
||||
"
|
||||
let x = new_ts();
|
||||
foo(x); // normal call to 'foo'
|
||||
x.foo() // 'foo' can also be called like a method on 'x'
|
||||
")?;
|
||||
|
||||
println!("result: {result}"); // prints 1
|
||||
```
|
||||
|
||||
|
||||
First `&mut` Parameter
|
||||
----------------------
|
||||
|
||||
The opposite direction also works — methods in a Rust [custom type] registered with the
|
||||
[`Engine`] can be called just like a regular function. In fact, like Rust, object methods are
|
||||
registered as regular [functions] in Rhai that take a first `&mut` parameter.
|
||||
|
||||
Unlike functions defined in script (for which all arguments are passed by _value_),
|
||||
native Rust functions may mutate the first `&mut` argument.
|
||||
|
||||
Sometimes, however, there are more subtle differences. Methods called in normal function-call style
|
||||
may end up not muting the object afterall — see the example below.
|
||||
|
||||
Custom types, [properties][getters/setters], [indexers] and methods are disabled under the
|
||||
[`no_object`] feature.
|
||||
|
||||
```rust
|
||||
let a = new_ts(); // constructor function
|
||||
a.field = 500; // property setter
|
||||
a.update(); // method call, 'a' can be modified
|
||||
|
||||
update(a); // <- this de-sugars to 'a.update()'
|
||||
// 'a' can be modified and is not a copy
|
||||
|
||||
let array = [ a ];
|
||||
|
||||
update(array[0]); // <- 'array[0]' is an expression returning a calculated value,
|
||||
// a transient (i.e. a copy), so this statement has no effect
|
||||
// except waste time cloning 'a'
|
||||
|
||||
array[0].update(); // <- call in method-call style will update 'a'
|
||||
```
|
||||
|
||||
```admonish danger.small "No support for references"
|
||||
|
||||
Rhai does NOT support normal references (i.e. `&T`) as parameters.
|
||||
All references must be mutable (i.e. `&mut T`).
|
||||
```
|
||||
|
||||
|
||||
Number of Parameters in Methods
|
||||
-------------------------------
|
||||
|
||||
Native Rust methods registered with an [`Engine`] take _one additional parameter_ more than
|
||||
an equivalent method coded in script, where the object is accessed via the `this` pointer instead.
|
||||
|
||||
The following table illustrates the differences:
|
||||
|
||||
| Function type | No. of parameters | Object reference | Function signature |
|
||||
| :-----------: | :---------------: | :----------------------: | :---------------------------: |
|
||||
| Native Rust | _N_ + 1 | first `&mut T` parameter | `Fn(obj: &mut T, x: U, y: V)` |
|
||||
| Rhai script | _N_ | `this` | `Fn(x: U, y: V)` |
|
||||
|
||||
|
||||
`&mut` is Efficient, Except for `&mut ImmutableString`
|
||||
------------------------------------------------------
|
||||
|
||||
Using a `&mut` first parameter is highly encouraged when using types that are expensive to clone,
|
||||
even when the intention is not to mutate that argument, because it avoids cloning that argument value.
|
||||
|
||||
Even when a function is never intended to be a method – for example an operator,
|
||||
it is still sometimes beneficial to make it method-like (i.e. with a first `&mut` parameter)
|
||||
if the first parameter is not modified.
|
||||
|
||||
For types that are expensive to clone (remember, all function calls are passed cloned
|
||||
copies of argument values), this may result in a significant performance boost.
|
||||
|
||||
For primary types that are cheap to clone (e.g. those that implement `Copy`), including `ImmutableString`,
|
||||
this is not necessary.
|
||||
|
||||
```rust
|
||||
// This is a type that is very expensive to clone.
|
||||
#[derive(Debug, Clone)]
|
||||
struct VeryComplexType { ... }
|
||||
|
||||
// Calculate some value by adding 'VeryComplexType' with an integer number.
|
||||
fn do_add(obj: &VeryComplexType, offset: i64) -> i64 {
|
||||
...
|
||||
}
|
||||
|
||||
engine.register_type::<VeryComplexType>()
|
||||
.register_fn("+", add_pure /* or add_method*/);
|
||||
|
||||
// Very expensive to call, as the 'VeryComplexType' is cloned before each call.
|
||||
fn add_pure(obj: VeryComplexType, offset: i64) -> i64 {
|
||||
do_add(obj, offset)
|
||||
}
|
||||
|
||||
// Efficient to call, as only a reference to the 'VeryComplexType' is passed.
|
||||
fn add_method(obj: &mut VeryComplexType, offset: i64) -> i64 {
|
||||
do_add(obj, offset)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Data Race Considerations
|
||||
------------------------
|
||||
|
||||
```admonish note.side "Data races"
|
||||
|
||||
Data races are not possible in Rhai under the [`no_closure`] feature because no sharing ever occurs.
|
||||
```
|
||||
|
||||
Because methods always take a mutable reference as the first argument, even it the value is never changed,
|
||||
care must be taken when using _shared_ values with methods.
|
||||
|
||||
Usually data races are not possible in Rhai because, for each function call, there is ever only one
|
||||
value that is mutable – the first argument of a method. All other arguments are cloned.
|
||||
|
||||
It is possible, however, to create a data race with a _shared_ value, when the same value is
|
||||
_captured_ in a [closure] and then used again as the _object_ of calling that [closure]!
|
||||
|
||||
```rust
|
||||
let x = 20;
|
||||
|
||||
x.is_shared() == false; // 'x' is not shared, so no data race is possible
|
||||
|
||||
let f = |a| this += x + a; // 'x' is captured in this closure
|
||||
|
||||
x.is_shared() == true; // now 'x' is shared
|
||||
|
||||
x.call(f, 2); // <- error: data race detected on 'x'
|
||||
```
|
58
rhai_engine/rhaibook/rust/methods.md
Normal file
58
rhai_engine/rhaibook/rust/methods.md
Normal file
@@ -0,0 +1,58 @@
|
||||
Methods
|
||||
=======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Methods of [custom types] are registered via `Engine::register_fn`.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct {
|
||||
field: i64
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn new() -> Self {
|
||||
Self { field: 1 }
|
||||
}
|
||||
|
||||
fn update(&mut self, x: i64) { // methods take &mut as first parameter
|
||||
self.field += x;
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Most Engine APIs can be chained up.
|
||||
engine.register_type_with_name::<TestStruct>("TestStruct")
|
||||
.register_fn("new_ts", TestStruct::new)
|
||||
.register_fn("update", TestStruct::update);
|
||||
|
||||
// Cast result back to custom type.
|
||||
let result = engine.eval::<TestStruct>(
|
||||
"
|
||||
let x = new_ts(); // calls 'TestStruct::new'
|
||||
x.update(41); // calls 'TestStruct::update'
|
||||
x // 'x' holds a 'TestStruct'
|
||||
")?;
|
||||
|
||||
println!("result: {}", result.field); // prints 42
|
||||
```
|
||||
|
||||
|
||||
First Parameter Must be `&mut`
|
||||
------------------------------
|
||||
|
||||
_Methods_ of [custom types] take a `&mut` first parameter to that type, so that invoking methods can
|
||||
always update it.
|
||||
|
||||
All other parameters in Rhai are passed by value (i.e. clones).
|
||||
|
||||
```admonish danger.small "No support for references"
|
||||
|
||||
Rhai does NOT support normal references (i.e. `&T`) as parameters.
|
||||
All references must be mutable (i.e. `&mut T`).
|
||||
```
|
88
rhai_engine/rhaibook/rust/modules/ast.md
Normal file
88
rhai_engine/rhaibook/rust/modules/ast.md
Normal file
@@ -0,0 +1,88 @@
|
||||
Create a Module from an AST
|
||||
===========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
`Module::eval_ast_as_new`
|
||||
-------------------------
|
||||
|
||||
```admonish info.side.wide "Encapsulated environment"
|
||||
|
||||
`Module::eval_ast_as_new` encapsulates the entire [`AST`] into each function call, merging the
|
||||
[module namespace][function namespace] with the [global namespace][function namespace].
|
||||
|
||||
Therefore, [functions] defined within the same [module] script can cross-call each other.
|
||||
```
|
||||
|
||||
```admonish info.side.wide "See also"
|
||||
|
||||
See [_Export Variables, Functions and Sub-Modules from Script_][`export`] for details on how to prepare
|
||||
a Rhai script for this purpose as well as to control which [functions]/[variables] to export.
|
||||
```
|
||||
|
||||
A [module] can be created from a single script (or pre-compiled [`AST`]) containing global
|
||||
[variables], [functions] and [sub-modules][module] via `Module::eval_ast_as_new`.
|
||||
|
||||
When given an [`AST`], it is first evaluated (usually to [import][`import`] [modules] and set up global
|
||||
[constants] used by [functions]), then the following items are exposed as members of the new [module]:
|
||||
|
||||
* global [variables] and [constants] – all [variables] and [constants] exported via the
|
||||
[`export`] statement (those not exported remain hidden),
|
||||
|
||||
* [functions] not specifically marked [`private`],
|
||||
|
||||
* imported [modules] that remain in the [`Scope`] at the end of a script run become sub-modules.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Don't forget the [`export`] statement, otherwise there will be no [variables] exposed by the
|
||||
[module] other than non-[`private`] [functions] (unless that's intentional).
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
// Compile a script into an 'AST'
|
||||
let ast = engine.compile(
|
||||
r#"
|
||||
// Functions become module functions
|
||||
fn calc(x) {
|
||||
x + add_len(x, 1) // functions within the same module
|
||||
// can always cross-call each other!
|
||||
}
|
||||
fn add_len(x, y) {
|
||||
x + y.len
|
||||
}
|
||||
|
||||
// Imported modules become sub-modules
|
||||
import "another module" as extra;
|
||||
|
||||
// Variables defined at global level can become module variables
|
||||
const x = 123;
|
||||
let foo = 41;
|
||||
let hello;
|
||||
|
||||
// Variable values become constant module variable values
|
||||
foo = calc(foo);
|
||||
hello = `hello, ${foo} worlds!`;
|
||||
|
||||
// Finally, export the variables and modules
|
||||
export x as abc; // aliased variable name
|
||||
export foo;
|
||||
export hello;
|
||||
"#)?;
|
||||
|
||||
// Convert the 'AST' into a module, using the 'Engine' to evaluate it first
|
||||
// A copy of the entire 'AST' is encapsulated into each function,
|
||||
// allowing functions in the module script to cross-call each other.
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||
|
||||
// 'module' now contains:
|
||||
// - sub-module: 'extra'
|
||||
// - functions: 'calc', 'add_len'
|
||||
// - constants: 'abc' (renamed from 'x'), 'foo', 'hello'
|
||||
```
|
24
rhai_engine/rhaibook/rust/modules/create.md
Normal file
24
rhai_engine/rhaibook/rust/modules/create.md
Normal file
@@ -0,0 +1,24 @@
|
||||
Create a Module in Rust
|
||||
=======================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
The Easy Way – Plugin
|
||||
---------------------------
|
||||
|
||||
By far the simplest way to create a [module] is via a [plugin module]
|
||||
which converts a normal Rust module into a Rhai [module] via procedural macros.
|
||||
|
||||
|
||||
The Hard Way – `Module` API
|
||||
---------------------------------
|
||||
|
||||
Manually creating a [module] is possible via the [`Module`] public API, which is volatile and may
|
||||
change from time to time.
|
||||
|
||||
~~~admonish info.small "`Module` public API"
|
||||
|
||||
For the complete [`Module`] public API, refer to the
|
||||
[documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Module.html) online.
|
||||
~~~
|
30
rhai_engine/rhaibook/rust/modules/index.md
Normal file
30
rhai_engine/rhaibook/rust/modules/index.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Modules
|
||||
=======
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Rhai allows organizing functionalities ([functions], both Rust-based and scripted, and
|
||||
[variables]) into independent _modules_.
|
||||
|
||||
A module has the type `rhai::Module` and holds a collection of [functions], [variables], [type
|
||||
iterators] and sub-modules.
|
||||
|
||||
It may contain entirely Rust functions, or it may encapsulate a Rhai script together with
|
||||
all the [functions] and [variables] defined by that script.
|
||||
|
||||
Other scripts then load this module and use the [functions] and [variables] [exported][`export`].
|
||||
|
||||
Alternatively, modules can be registered directly into an [`Engine`] and made available to scripts,
|
||||
either globally or under individual static module [_namespaces_][function namespaces].
|
||||
|
||||
Modules can be disabled via the [`no_module`] feature.
|
||||
|
||||
|
||||
Usage Patterns
|
||||
--------------
|
||||
|
||||
| Usage | API | Lookup | Sub-modules? | Variables? |
|
||||
| -------------- | :-------------------------------: | :----------------------: | :----------: | :--------: |
|
||||
| Global module | `Engine:: register_global_module` | simple name | ignored | yes |
|
||||
| Static module | `Engine:: register_static_module` | namespace-qualified name | yes | yes |
|
||||
| Dynamic module | [`import`] statement | namespace-qualified name | yes | yes |
|
10
rhai_engine/rhaibook/rust/modules/resolvers/built-in.md
Normal file
10
rhai_engine/rhaibook/rust/modules/resolvers/built-in.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Built-in Module Resolvers
|
||||
=========================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
There are a number of standard [module resolvers] built into Rhai, the default being the
|
||||
[`FileModuleResolver`](file.md) which simply loads a script file based on the path (with `.rhai`
|
||||
extension attached) and execute it to form a [module].
|
||||
|
||||
Built-in [module resolvers] are grouped under the `rhai::module_resolvers` module.
|
11
rhai_engine/rhaibook/rust/modules/resolvers/collection.md
Normal file
11
rhai_engine/rhaibook/rust/modules/resolvers/collection.md
Normal file
@@ -0,0 +1,11 @@
|
||||
`ModuleResolversCollection`
|
||||
===========================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
|
||||
A collection of [module resolvers].
|
||||
|
||||
[Modules] are resolved from each resolver in sequential order.
|
||||
|
||||
This is useful when multiple types of [modules] are needed simultaneously.
|
95
rhai_engine/rhaibook/rust/modules/resolvers/custom.md
Normal file
95
rhai_engine/rhaibook/rust/modules/resolvers/custom.md
Normal file
@@ -0,0 +1,95 @@
|
||||
Implement a Custom Module Resolver
|
||||
==================================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
For many applications in which Rhai is embedded, it is necessary to customize the way that [modules]
|
||||
are resolved. For instance, [modules] may need to be loaded from script texts stored in a database,
|
||||
not in the file system.
|
||||
|
||||
A module resolver must implement the [`ModuleResolver`][traits] trait, which contains only one
|
||||
required function: `resolve`.
|
||||
|
||||
When Rhai prepares to load a module, `ModuleResolver::resolve` is called with the name
|
||||
of the _module path_ (i.e. the path specified in the [`import`] statement).
|
||||
|
||||
```admonish success
|
||||
Upon success, it should return a shared [module] wrapped by `Rc` (or `Arc` under [`sync`]).
|
||||
|
||||
The module resolver should call `Module::build_index` on the target [module] before returning it.
|
||||
* This method flattens the entire module tree and _indexes_ it for fast function name resolution.
|
||||
* If the module is already indexed, calling this method has no effect.
|
||||
```
|
||||
|
||||
```admonish failure
|
||||
|
||||
* If the path does not resolve to a valid module, return `EvalAltResult::ErrorModuleNotFound`.
|
||||
|
||||
* If the module failed to load, return `EvalAltResult::ErrorInModule`.
|
||||
```
|
||||
|
||||
Example of a Custom Module Resolver
|
||||
-----------------------------------
|
||||
|
||||
```rust
|
||||
use rhai::{ModuleResolver, Module, Engine, EvalAltResult};
|
||||
|
||||
// Define a custom module resolver.
|
||||
struct MyModuleResolver {}
|
||||
|
||||
// Implement the 'ModuleResolver' trait.
|
||||
impl ModuleResolver for MyModuleResolver {
|
||||
// Only required function.
|
||||
fn resolve(
|
||||
&self,
|
||||
engine: &Engine, // reference to the current 'Engine'
|
||||
source_path: Option<&str>, // path of the parent module
|
||||
path: &str, // the module path
|
||||
pos: Position, // position of the 'import' statement
|
||||
) -> Result<Rc<Module>, Box<EvalAltResult>> {
|
||||
// Check module path.
|
||||
if is_valid_module_path(path) {
|
||||
// Load the custom module
|
||||
match load_secret_module(path) {
|
||||
Ok(my_module) => {
|
||||
my_module.build_index(); // index it
|
||||
Rc::new(my_module) // make it shared
|
||||
},
|
||||
// Return 'EvalAltResult::ErrorInModule' upon loading error
|
||||
Err(err) => Err(EvalAltResult::ErrorInModule(path.into(), Box::new(err), pos).into())
|
||||
}
|
||||
} else {
|
||||
// Return 'EvalAltResult::ErrorModuleNotFound' if the path is invalid
|
||||
Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Set the custom module resolver into the 'Engine'.
|
||||
engine.set_module_resolver(MyModuleResolver {});
|
||||
|
||||
engine.run(
|
||||
r#"
|
||||
import "hello" as foo; // this 'import' statement will call
|
||||
// 'MyModuleResolver::resolve' with "hello" as 'path'
|
||||
foo:bar();
|
||||
"#)?;
|
||||
```
|
||||
|
||||
|
||||
Advanced – `ModuleResolver::resolve_ast`
|
||||
----------------------------------------------
|
||||
|
||||
There is another function in the [`ModuleResolver`][traits] trait, `resolve_ast`, which is a
|
||||
low-level API intended for advanced usage scenarios.
|
||||
|
||||
`ModuleResolver::resolve_ast` has a default implementation that simply returns `None`,
|
||||
which indicates that this API is not supported by the [module resolver].
|
||||
|
||||
Any [module resolver] that serves [modules] based on Rhai scripts should implement
|
||||
`ModuleResolver::resolve_ast`. When called, the compiled [`AST`] of the script should be returned.
|
||||
|
||||
`ModuleResolver::resolve_ast` should not return an error if `ModuleResolver::resolve` will not.
|
||||
On the other hand, the same error should be returned if `ModuleResolver::resolve` will return one.
|
11
rhai_engine/rhaibook/rust/modules/resolvers/dummy.md
Normal file
11
rhai_engine/rhaibook/rust/modules/resolvers/dummy.md
Normal file
@@ -0,0 +1,11 @@
|
||||
`DummyModuleResolver`
|
||||
=====================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
```admonish abstract.small "Default"
|
||||
|
||||
`DummyModuleResolver` is the default for [`no_std`] or [`Engine::new_raw`][raw `Engine`].
|
||||
```
|
||||
|
||||
This [module resolver] acts as a _dummy_ and fails all module resolution calls.
|
64
rhai_engine/rhaibook/rust/modules/resolvers/dylib.md
Normal file
64
rhai_engine/rhaibook/rust/modules/resolvers/dylib.md
Normal file
@@ -0,0 +1,64 @@
|
||||
`DylibModuleResolver`
|
||||
=====================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
|
||||
~~~admonish warning.small "Requires external crate `rhai-dylib`"
|
||||
|
||||
`DylibModuleResolver` resides in the [`rhai-dylib`] crate which must be specified
|
||||
as a dependency:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rhai-dylib = { version = "0.1" }
|
||||
```
|
||||
~~~
|
||||
|
||||
```admonish danger.small "Linux or Windows only"
|
||||
|
||||
[`rhai-dylib`] currently supports only Linux and Windows.
|
||||
```
|
||||
|
||||
Parallel to how the [`FileModuleResolver`](file.md) works, `DylibModuleResolver` loads external
|
||||
native Rust [modules] from compiled _dynamic shared libraries_ (e.g. `.so` in Linux and `.dll` in
|
||||
Windows).
|
||||
|
||||
Therefore, [`FileModuleResolver`](file.md`) loads Rhai script files while `DylibModuleResolver`
|
||||
loads native Rust shared libraries. It is very common to have the two work together.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module};
|
||||
use rhai::module_resolvers::{FileModuleResolver, ModuleResolversCollection};
|
||||
use rhai_dylib::module_resolvers::DylibModuleResolver;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Use a module resolvers collection
|
||||
let mut resolvers = ModuleResolversCollection::new();
|
||||
|
||||
// First search for script files in the file system
|
||||
resolvers += FileModuleResolver::new();
|
||||
|
||||
// Then search for shared-library plugins in the file system
|
||||
resolvers += DylibModuleResolver::new();
|
||||
|
||||
// Set the module resolver into the engine
|
||||
engine.set_module_resolver(resolvers);
|
||||
|
||||
|
||||
┌─────────────┐
|
||||
│ Rhai Script │
|
||||
└─────────────┘
|
||||
|
||||
// If there is 'path/to/my_module.rhai', load it.
|
||||
// Otherwise, check for 'path/to/my_module.so' on Linux
|
||||
// ('path/to/my_module.dll' on Windows).
|
||||
import "path/to/my_module" as m;
|
||||
|
||||
m::greet();
|
||||
```
|
183
rhai_engine/rhaibook/rust/modules/resolvers/file.md
Normal file
183
rhai_engine/rhaibook/rust/modules/resolvers/file.md
Normal file
@@ -0,0 +1,183 @@
|
||||
`FileModuleResolver`
|
||||
====================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
|
||||
```admonish abstract.small "Default"
|
||||
|
||||
`FileModuleResolver` is the default for [`Engine::new`][`Engine`].
|
||||
```
|
||||
|
||||
The _default_ [module] resolution service, not available for [`no_std`] or [WASM] builds.
|
||||
Loads a script file (based off the current directory or a specified one) with `.rhai` extension.
|
||||
|
||||
|
||||
Function Namespace
|
||||
-------------------
|
||||
|
||||
All functions in the [_global_ namespace][function namespace], plus all those defined in the same
|
||||
[module], are _merged_ into a _unified_ [namespace][function namespace].
|
||||
|
||||
All [modules] imported at _global_ level via [`import`] statements become sub-[modules],
|
||||
which are also available to [functions] defined within the same script file.
|
||||
|
||||
|
||||
Base Directory
|
||||
--------------
|
||||
|
||||
```admonish tip.side.wide "Tip: Default"
|
||||
|
||||
If the base directory is not set, then relative paths are based off the directory of the loading script.
|
||||
|
||||
This allows scripts to simply cross-load each other.
|
||||
```
|
||||
|
||||
_Relative_ paths are resolved relative to a _root_ directory, which is usually the base directory.
|
||||
|
||||
The base directory can be set via `FileModuleResolver::new_with_path` or
|
||||
`FileModuleResolver::set_base_path`.
|
||||
|
||||
|
||||
Custom [`Scope`]
|
||||
----------------
|
||||
|
||||
```admonish tip.side.wide "Tip"
|
||||
|
||||
This [`Scope`] can conveniently hold global [constants] etc.
|
||||
```
|
||||
|
||||
The `set_scope` method adds an optional [`Scope`] which will be used to [optimize][script optimization] [module] scripts.
|
||||
|
||||
|
||||
Caching
|
||||
-------
|
||||
|
||||
```admonish tip.side.wide "Tip: Enable/disable caching"
|
||||
|
||||
Use `enable_cache` to enable/disable the cache.
|
||||
```
|
||||
|
||||
By default, [modules] are also _cached_ so a script file is only evaluated _once_, even when
|
||||
repeatedly imported.
|
||||
|
||||
|
||||
Unix Shebangs
|
||||
-------------
|
||||
|
||||
On Unix-like systems, the _shebang_ (`#!`) is used at the very beginning of a script file to mark a
|
||||
script with an interpreter (for Rhai this would be [`rhai-run`]({{rootUrl}}/start/bin.md)).
|
||||
|
||||
If a script file starts with `#!`, the entire first line is skipped.
|
||||
Because of this, Rhai scripts with shebangs at the beginning need no special processing.
|
||||
|
||||
```js
|
||||
#!/home/to/me/bin/rhai-run
|
||||
|
||||
// This is a Rhai script
|
||||
|
||||
let answer = 42;
|
||||
print(`The answer is: ${answer}`);
|
||||
```
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
|
||||
```rust
|
||||
┌────────────────┐
|
||||
│ my_module.rhai │
|
||||
└────────────────┘
|
||||
|
||||
// This function overrides any in the main script.
|
||||
private fn inner_message() { "hello! from module!" }
|
||||
|
||||
fn greet() {
|
||||
print(inner_message()); // call function in module script
|
||||
}
|
||||
|
||||
fn greet_main() {
|
||||
print(main_message()); // call function not in module script
|
||||
}
|
||||
|
||||
|
||||
┌───────────┐
|
||||
│ main.rhai │
|
||||
└───────────┘
|
||||
|
||||
// This function is overridden by the module script.
|
||||
fn inner_message() { "hi! from main!" }
|
||||
|
||||
// This function is found by the module script.
|
||||
fn main_message() { "main here!" }
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(); // prints "hello! from module!"
|
||||
|
||||
m::greet_main(); // prints "main here!"
|
||||
```
|
||||
|
||||
|
||||
Simulate Virtual Functions
|
||||
--------------------------
|
||||
|
||||
When calling a namespace-qualified [function] defined within a [module], other [functions] defined
|
||||
within the same module override any similar-named [functions] (with the same number of parameters)
|
||||
defined in the [global namespace][function namespace].
|
||||
|
||||
This is to ensure that a [module] acts as a self-contained unit and [functions] defined in the
|
||||
calling script do not override [module] code.
|
||||
|
||||
In some situations, however, it is actually beneficial to do it in reverse: have [module] [functions] call
|
||||
[functions] defined in the calling script (i.e. in the [global namespace][function namespace]) if
|
||||
they exist, and only call those defined in the [module] if none are found.
|
||||
|
||||
One such situation is the need to provide a _default implementation_ to a simulated _virtual_ function:
|
||||
|
||||
```rust
|
||||
┌────────────────┐
|
||||
│ my_module.rhai │
|
||||
└────────────────┘
|
||||
|
||||
// Do not do this (it will override the main script):
|
||||
// fn message() { "hello! from module!" }
|
||||
|
||||
// This function acts as the default implementation.
|
||||
private fn default_message() { "hello! from module!" }
|
||||
|
||||
// This function depends on a 'virtual' function 'message'
|
||||
// which is not defined in the module script.
|
||||
fn greet() {
|
||||
if is_def_fn("message", 0) { // 'is_def_fn' detects if 'message' is defined.
|
||||
print(message());
|
||||
} else {
|
||||
print(default_message());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
┌───────────┐
|
||||
│ main.rhai │
|
||||
└───────────┘
|
||||
|
||||
// The main script defines 'message' which is needed by the module script.
|
||||
fn message() { "hi! from main!" }
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(); // prints "hi! from main!"
|
||||
|
||||
|
||||
┌────────────┐
|
||||
│ main2.rhai │
|
||||
└────────────┘
|
||||
|
||||
// The main script does not define 'message' which is needed by the module script.
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(); // prints "hello! from module!"
|
||||
```
|
||||
|
35
rhai_engine/rhaibook/rust/modules/resolvers/index.md
Normal file
35
rhai_engine/rhaibook/rust/modules/resolvers/index.md
Normal file
@@ -0,0 +1,35 @@
|
||||
Module Resolvers
|
||||
================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
~~~admonish info.side "`import`"
|
||||
|
||||
See the section on [_Importing Modules_][`import`] for more details.
|
||||
~~~
|
||||
|
||||
When encountering an [`import`] statement, Rhai attempts to _resolve_ the [module] based on the path string.
|
||||
|
||||
_Module Resolvers_ are service types that implement the [`ModuleResolver`][traits] trait.
|
||||
|
||||
|
||||
Set into `Engine`
|
||||
-----------------
|
||||
|
||||
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
||||
|
||||
```rust
|
||||
use rhai::module_resolvers::{DummyModuleResolver, StaticModuleResolver};
|
||||
|
||||
// Create a module resolver
|
||||
let resolver = StaticModuleResolver::new();
|
||||
|
||||
// Register functions into 'resolver'...
|
||||
|
||||
// Use the module resolver
|
||||
engine.set_module_resolver(resolver);
|
||||
|
||||
// Effectively disable 'import' statements by setting module resolver to
|
||||
// the 'DummyModuleResolver' which acts as... well... a dummy.
|
||||
engine.set_module_resolver(DummyModuleResolver::new());
|
||||
```
|
26
rhai_engine/rhaibook/rust/modules/resolvers/static.md
Normal file
26
rhai_engine/rhaibook/rust/modules/resolvers/static.md
Normal file
@@ -0,0 +1,26 @@
|
||||
`StaticModuleResolver`
|
||||
======================
|
||||
|
||||
{{#include ../../../links.md}}
|
||||
|
||||
|
||||
~~~admonish abstract.small "Useful for `no-std`"
|
||||
|
||||
`StaticModuleResolver` is often used with [`no_std`] in embedded environments
|
||||
without a file system.
|
||||
~~~
|
||||
|
||||
Loads [modules] that are statically added.
|
||||
|
||||
Functions are searched in the [_global_ namespace][function namespace] by default.
|
||||
|
||||
```rust
|
||||
use rhai::{Module, module_resolvers::StaticModuleResolver};
|
||||
|
||||
let module: Module = create_a_module();
|
||||
|
||||
let mut resolver = StaticModuleResolver::new();
|
||||
resolver.insert("my_module", module);
|
||||
|
||||
engine.set_module_resolver(resolver);
|
||||
```
|
70
rhai_engine/rhaibook/rust/modules/self-contained.md
Normal file
70
rhai_engine/rhaibook/rust/modules/self-contained.md
Normal file
@@ -0,0 +1,70 @@
|
||||
Compile to a Self-Contained `AST`
|
||||
=================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish tip.side "Tip"
|
||||
|
||||
It does not matter where the [`import`] statement occurs — e.g. deep within statements blocks
|
||||
or within [function] bodies.
|
||||
```
|
||||
|
||||
When a script [imports][`import`] external [modules] that may not be available later on, it is
|
||||
possible to eagerly [_pre-resolve_][module resolver] these imports and embed them directly into a
|
||||
self-contained [`AST`].
|
||||
|
||||
For instance, a system may periodically connect to a central source (e.g. a database) to load
|
||||
scripts and compile them to [`AST`] form. Afterwards, in order to conserve bandwidth (or due to
|
||||
other physical limitations), it is disconnected from the central source for self-contained
|
||||
operation.
|
||||
|
||||
Compile a script into a _self-contained_ [`AST`] via `Engine::compile_into_self_contained`.
|
||||
|
||||
```rust
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Compile script into self-contained AST using the current
|
||||
// module resolver (default to `FileModuleResolver`) to pre-resolve
|
||||
// 'import' statements.
|
||||
let ast = engine.compile_into_self_contained(&mut scope, script)?;
|
||||
|
||||
// Make sure we can no longer resolve any module!
|
||||
engine.set_module_resolver(DummyModuleResolver::new());
|
||||
|
||||
// The AST still evaluates fine, even with 'import' statements!
|
||||
engine.run(&ast)?;
|
||||
```
|
||||
|
||||
When such an [`AST`] is evaluated, [`import`] statements within are provided the _pre-resolved_
|
||||
[modules] without going through the normal [module resolution][module resolver] process.
|
||||
|
||||
|
||||
Only Static Paths
|
||||
-----------------
|
||||
|
||||
`Engine::compile_into_self_contained` only pre-resolves [`import`] statements in the script
|
||||
that are _static_, i.e. with a path that is a [string] literal.
|
||||
|
||||
```rust
|
||||
// The following import is pre-resolved.
|
||||
import "hello" as h;
|
||||
|
||||
if some_event() {
|
||||
// The following import is pre-resolved.
|
||||
import "hello" as h;
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
// The following import is pre-resolved.
|
||||
import "hello" as h;
|
||||
}
|
||||
|
||||
// The following import is also pre-resolved because the expression
|
||||
// is usually optimized into a single string during compilation.
|
||||
import "he" + "llo" as h;
|
||||
|
||||
let module_name = "hello";
|
||||
|
||||
// The following import is NOT pre-resolved.
|
||||
import module_name as h;
|
||||
```
|
183
rhai_engine/rhaibook/rust/modules/use.md
Normal file
183
rhai_engine/rhaibook/rust/modules/use.md
Normal file
@@ -0,0 +1,183 @@
|
||||
Make a Module Available to Scripts
|
||||
==================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
Use Case 1 – Make It Globally Available
|
||||
---------------------------------------------
|
||||
|
||||
`Engine::register_global_module` registers a shared [module] into the
|
||||
[_global_ namespace][function namespace].
|
||||
|
||||
This is by far the easiest way to expose a [module]'s functionalities to Rhai.
|
||||
|
||||
```admonish tip.small "Tip: No qualifiers"
|
||||
|
||||
All [functions], [variables]/[constants] and [type iterators] can be accessed without
|
||||
_namespace qualifiers_.
|
||||
```
|
||||
|
||||
```admonish warning.small
|
||||
|
||||
[Sub-modules][module] are **ignored**.
|
||||
```
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module, FuncRegistration};
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
|
||||
// Add new function.
|
||||
FuncRegistration::new("inc")
|
||||
.with_params_info(&["x: i64", "i64"])
|
||||
.set_into_module(&mut module, |x: i64| x + 1);
|
||||
|
||||
// Use 'Module::set_var' to add variables.
|
||||
module.set_var("MYSTIC_NUMBER", 41_i64);
|
||||
|
||||
// Register the module into the global namespace of the Engine.
|
||||
let mut engine = Engine::new();
|
||||
engine.register_global_module(module.into());
|
||||
|
||||
// No need to import module...
|
||||
engine.eval::<i64>("inc(MYSTIC_NUMBER)")? == 42;
|
||||
```
|
||||
|
||||
### Equivalent to `Engine::register_XXX`
|
||||
|
||||
```admonish question.side "Trivia"
|
||||
|
||||
`Engine::register_fn` etc. are actually implemented by adding functions to an
|
||||
internal [module]!
|
||||
```
|
||||
|
||||
Registering a [module] via `Engine::register_global_module` is essentially the _same_
|
||||
as calling `Engine::register_fn` (or any of the `Engine::register_XXX` API) individually
|
||||
on each top-level function within that [module].
|
||||
|
||||
```rust
|
||||
// The above is essentially the same as:
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_fn("inc", |x: i64| x + 1);
|
||||
|
||||
engine.eval::<i64>("inc(41)")? == 42; // no need to import module
|
||||
```
|
||||
|
||||
|
||||
Use Case 2 – Make It a Static Namespace
|
||||
---------------------------------------------
|
||||
|
||||
`Engine::register_static_module` registers a [module] and under a specific
|
||||
[module namespace][function namespace].
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module, FuncRegistration};
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
|
||||
// Add new function.
|
||||
FuncRegistration::new("inc")
|
||||
.with_params_info(&["x: i64", "i64"])
|
||||
.set_into_module(&mut module, |x: i64| x + 1);
|
||||
|
||||
// Use 'Module::set_var' to add variables.
|
||||
module.set_var("MYSTIC_NUMBER", 41_i64);
|
||||
|
||||
// Register the module into the Engine as the static module namespace path
|
||||
// 'services::calc'
|
||||
let mut engine = Engine::new();
|
||||
engine.register_static_module("services::calc", module.into());
|
||||
|
||||
// Refer to the 'services::calc' module...
|
||||
engine.eval::<i64>("services::calc::inc(services::calc::MYSTIC_NUMBER)")? == 42;
|
||||
```
|
||||
|
||||
### Expose functions to the global namespace
|
||||
|
||||
```admonish tip.side "Tip: Type iterators"
|
||||
|
||||
[Type iterators] are special — they are _always_ exposed to the
|
||||
[_global_ namespace][function namespace].
|
||||
```
|
||||
|
||||
The [`Module`] API can optionally expose functions to the [_global_ namespace][function namespace]
|
||||
by setting the `namespace` parameter to `FnNamespace::Global`.
|
||||
|
||||
This way, [getters/setters] and [indexers] for [custom types] can work as expected.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module, FuncRegistration, FnNamespace};
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
|
||||
// Add new function.
|
||||
FuncRegistration::new("inc")
|
||||
.with_params_info(&["x: i64", "i64"])
|
||||
.with_namespace(FnNamespace::Global) // <- global namespace
|
||||
.set_into_module(&mut module, |x: i64| x + 1);
|
||||
|
||||
// Use 'Module::set_var' to add variables.
|
||||
module.set_var("MYSTIC_NUMBER", 41_i64);
|
||||
|
||||
// Register the module into the Engine as a static module namespace 'calc'
|
||||
let mut engine = Engine::new();
|
||||
engine.register_static_module("calc", module.into());
|
||||
|
||||
// 'inc' works when qualified by the namespace
|
||||
engine.eval::<i64>("calc::inc(calc::MYSTIC_NUMBER)")? == 42;
|
||||
|
||||
// 'inc' also works without a namespace qualifier
|
||||
// because it is exposed to the global namespace
|
||||
engine.eval::<i64>("let x = calc::MYSTIC_NUMBER; x.inc()")? == 42;
|
||||
engine.eval::<i64>("let x = calc::MYSTIC_NUMBER; inc(x)")? == 42;
|
||||
```
|
||||
|
||||
|
||||
Use Case 3 – Make It Dynamically Loadable
|
||||
-----------------------------------------------
|
||||
|
||||
In order to dynamically load a custom module, there must be a [module resolver] which serves
|
||||
the module when loaded via `import` statements.
|
||||
|
||||
The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such
|
||||
a custom module.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope, Module, FuncRegistration};
|
||||
use rhai::module_resolvers::StaticModuleResolver;
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
|
||||
module.set_var("answer", 41_i64); // variable 'answer' under module
|
||||
|
||||
FuncRegistration::new("inc")
|
||||
.with_params_info(&["x: i64"])
|
||||
.set_into_module(&mut module, |x: i64| x + 1);
|
||||
|
||||
// Create the module resolver
|
||||
let mut resolver = StaticModuleResolver::new();
|
||||
|
||||
// Add the module into the module resolver under the name 'question'
|
||||
// They module can then be accessed via: 'import "question" as q;'
|
||||
resolver.insert("question", module);
|
||||
|
||||
// Set the module resolver into the 'Engine'
|
||||
let mut engine = Engine::new();
|
||||
engine.set_module_resolver(resolver);
|
||||
|
||||
// Use namespace-qualified variables
|
||||
engine.eval::<i64>(
|
||||
r#"
|
||||
import "question" as q;
|
||||
q::answer + 1
|
||||
"#)? == 42;
|
||||
|
||||
// Call namespace-qualified functions
|
||||
engine.eval::<i64>(
|
||||
r#"
|
||||
import "question" as q;
|
||||
q::inc(q::answer)
|
||||
"#)? == 42;
|
||||
```
|
91
rhai_engine/rhaibook/rust/operators.md
Normal file
91
rhai_engine/rhaibook/rust/operators.md
Normal file
@@ -0,0 +1,91 @@
|
||||
Operator Overloading
|
||||
====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
In Rhai, a lot of functionalities are actually implemented as functions, including basic operations
|
||||
such as arithmetic calculations.
|
||||
|
||||
For example, in the expression "`a + b`", the `+` [operator] actually calls a function named "`+`"!
|
||||
|
||||
```rust
|
||||
let x = a + b;
|
||||
|
||||
let x = +(a, b); // <- the above is equivalent to this function call
|
||||
```
|
||||
|
||||
Similarly, comparison [operators] including `==`, `!=` etc. are all implemented as functions,
|
||||
with the stark exception of `&&`, `||` and `??`.
|
||||
|
||||
~~~admonish warning.small "`&&`, `||` and `??` cannot be overloaded"
|
||||
|
||||
Because they [_short-circuit_]({{rootUrl}}/language/logic.md#boolean-operators), `&&`, `||` and `??` are
|
||||
handled specially and _not_ via a function.
|
||||
|
||||
Overriding them has no effect at all.
|
||||
~~~
|
||||
|
||||
|
||||
Overload Operator via Rust Function
|
||||
-----------------------------------
|
||||
|
||||
[Operator] functions cannot be defined in script because [operators] are usually not valid function names.
|
||||
|
||||
However, [operator] functions _can_ be registered via `Engine::register_fn`.
|
||||
|
||||
When a custom [operator] function is registered with the same name as an [operator],
|
||||
it _overrides_ the built-in version. However, make sure the [_Fast Operators Mode_][fast operators]
|
||||
is disabled; otherwise this will not work.
|
||||
|
||||
```admonish warning.small "Must turn off _Fast Operators Mode_"
|
||||
|
||||
The [_Fast Operators Mode_][fast operators], which is enabled by default, causes the [`Engine`]
|
||||
to _ignore_ all custom-registered operator functions for [built-in operators]. This is for
|
||||
performance considerations.
|
||||
|
||||
Disable [_Fast Operators Mode_][fast operators] via [`Engine::set_fast_operators`][options]
|
||||
in order for the overloaded operators to be used.
|
||||
```
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
fn strange_add(a: i64, b: i64) -> i64 {
|
||||
(a + b) * 42
|
||||
}
|
||||
|
||||
engine.register_fn("+", strange_add); // overload '+' operator for two integers!
|
||||
|
||||
engine.set_fast_operators(false); // <- IMPORTANT! must turn off Fast Operators Mode
|
||||
|
||||
let result: i64 = engine.eval("1 + 0"); // the overloading version is used
|
||||
|
||||
result == 42;
|
||||
|
||||
let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded
|
||||
|
||||
result == 1.0;
|
||||
|
||||
fn mixed_add(a: i64, b: bool) -> f64 { a + if b { 42 } else { 99 } }
|
||||
|
||||
engine.register_fn("+", mixed_add); // register '+' operator for an integer and a bool
|
||||
|
||||
let result: i64 = engine.eval("1 + true"); // <- normally an error...
|
||||
|
||||
result == 43; // ... but not now
|
||||
```
|
||||
|
||||
```admonish danger.small "Considerations"
|
||||
|
||||
Use [operator] overloading for [custom types] only.
|
||||
|
||||
Be **very careful** when overriding built-in [operators] because users expect standard [operators] to
|
||||
behave in a consistent and predictable manner, and will be annoyed if an expression involving `+`
|
||||
turns into a subtraction, for example. You may think it is amusing, but users who need to get
|
||||
things done won't.
|
||||
|
||||
[Operator] overloading also impacts [script optimization] when using [`OptimizationLevel::Full`].
|
||||
See the section on [script optimization] for more details.
|
||||
```
|
34
rhai_engine/rhaibook/rust/overloading.md
Normal file
34
rhai_engine/rhaibook/rust/overloading.md
Normal file
@@ -0,0 +1,34 @@
|
||||
Function Overloading
|
||||
====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique,
|
||||
i.e. different functions can have the same name as long as their parameters are of different numbers
|
||||
(i.e. _arity_) or different types.
|
||||
|
||||
New definitions _overwrite_ previous definitions of the same name, same arity and same parameter types.
|
||||
|
||||
~~~admonish tip "Tip: Overloading as a form of default parameter values"
|
||||
|
||||
Rhai does not support default values for function parameters.
|
||||
|
||||
However it is extremely easy to _simulate_ default parameter values via multiple overloaded
|
||||
registrations of the same function name.
|
||||
|
||||
```rust
|
||||
// The following definition of 'foo' is equivalent to the pseudo-code:
|
||||
// fn foo(x = 42_i64, y = "hello", z = true) -> i64 { ... }
|
||||
|
||||
fn foo3(x: i64, y: &str, z: bool) -> i64 { ... }
|
||||
fn foo2(x: i64, y: &str) -> i64 { foo3(x, y, true) }
|
||||
fn foo1(x: i64) -> i64 { foo2(x, "hello") }
|
||||
fn foo0() -> i64 { foo1(42) }
|
||||
|
||||
engine.register_fn("foo", foo0) // no parameters
|
||||
.register_fn("foo", foo1) // 1 parameter
|
||||
.register_fn("foo", foo2) // 2 parameters
|
||||
.register_fn("foo", foo3); // 3 parameters
|
||||
```
|
||||
~~~
|
55
rhai_engine/rhaibook/rust/override.md
Normal file
55
rhai_engine/rhaibook/rust/override.md
Normal file
@@ -0,0 +1,55 @@
|
||||
Override a Built-in Function
|
||||
============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Any similarly-named function defined in a script _overrides_ any built-in or registered
|
||||
native Rust function of the same name and number of parameters.
|
||||
|
||||
```js
|
||||
// Override the built-in function 'to_float' when called as a method
|
||||
fn to_float() {
|
||||
print(`Ha! Gotcha! ${this}`);
|
||||
42.0
|
||||
}
|
||||
|
||||
let x = 123.to_float();
|
||||
|
||||
print(x); // what happens?
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Monkey patching Rhai"
|
||||
|
||||
Most of Rhai's built-in functionality resides in registered functions.
|
||||
|
||||
If you dislike any built-in function, simply provide your own implementation to
|
||||
_override_ the built-in version.
|
||||
|
||||
The ability to modify the operating environment dynamically at runtime is called
|
||||
"[monkey patching](https://en.wikipedia.org/wiki/Monkey_patch)."
|
||||
It is rarely recommended, but if you need it, you need it bad.
|
||||
|
||||
In other words, do it only when _all else fails_. Do not monkey patch Rhai simply
|
||||
because you _can_.
|
||||
```
|
||||
|
||||
```admonish info.small "Search order for functions"
|
||||
|
||||
Rhai searches for the correct implementation of a function in the following order:
|
||||
|
||||
1. Rhai script-defined [functions],
|
||||
|
||||
2. Native Rust functions registered directly via the `Engine::register_XXX` API,
|
||||
|
||||
3. Native Rust functions in [packages] that have been loaded via `Engine::register_global_module`,
|
||||
|
||||
4. Native Rust or Rhai script-defined functions in [imported][`import`] [modules] that are exposed to
|
||||
the global [namespace][function namespace] (e.g. via the `#[rhai_fn(global)]` attribute in a
|
||||
[plugin module]),
|
||||
|
||||
5. Native Rust or Rhai script-defined functions in [modules] loaded via
|
||||
`Engine::register_static_module` that are exposed to the global [namespace][function namespace]
|
||||
(e.g. via the `#[rhai_fn(global)]` attribute in a [plugin module]),
|
||||
|
||||
6. [Built-in][built-in operators] functions.
|
||||
```
|
44
rhai_engine/rhaibook/rust/packages/builtin.md
Normal file
44
rhai_engine/rhaibook/rust/packages/builtin.md
Normal file
@@ -0,0 +1,44 @@
|
||||
Built-In Packages
|
||||
=================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
`Engine::new` creates an [`Engine`] with the `StandardPackage` loaded.
|
||||
|
||||
`Engine::new_raw` creates an [`Engine`] with _no_ [package] loaded.
|
||||
|
||||
| Package | Description | In `Core` | In `Standard` |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------- | :-------: | :-----------: |
|
||||
| `LanguageCorePackage` | core functions for the Rhai language | yes | yes |
|
||||
| `ArithmeticPackage` | arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | yes | yes |
|
||||
| `BitFieldPackage` | basic [bit-field] functions | **no** | yes |
|
||||
| `BasicIteratorPackage` | numeric ranges (e.g. `range(1, 100, 5)`), iterators for [arrays], [strings], [bit-fields] and [object maps] | yes | yes |
|
||||
| `LogicPackage` | logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | **no** | yes |
|
||||
| `BasicStringPackage` | basic string functions (e.g. `print`, `debug`, `len`) that are not built in | yes | yes |
|
||||
| `BasicTimePackage` | basic time functions (e.g. [timestamps], not available under [`no_time`] or [`no_std`]) | **no** | yes |
|
||||
| `MoreStringPackage` | additional string functions, including converting common types to string | **no** | yes |
|
||||
| `BasicMathPackage` | basic math functions (e.g. `sin`, `sqrt`) | **no** | yes |
|
||||
| `BasicArrayPackage` | basic [array] functions (not available under [`no_index`]) | **no** | yes |
|
||||
| `BasicBlobPackage` | basic [BLOB] functions (not available under [`no_index`]) | **no** | yes |
|
||||
| `BasicMapPackage` | basic [object map] functions (not available under [`no_object`]) | **no** | yes |
|
||||
| `BasicFnPackage` | basic methods for [function pointers] | yes | yes |
|
||||
| `DebuggingPackage` | basic functions for [debugging][debugger] (requires [`debugging`]) | yes | yes |
|
||||
| `CorePackage` | basic essentials | yes | yes |
|
||||
| `StandardPackage` | standard library (default for `Engine::new`) | **no** | yes |
|
||||
|
||||
|
||||
`CorePackage`
|
||||
-------------
|
||||
|
||||
If only minimal functionalities are required, register the `CorePackage` instead.
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
use rhai::packages::{Package, CorePackage};
|
||||
|
||||
let mut engine = Engine::new_raw();
|
||||
let package = CorePackage::new();
|
||||
|
||||
// Register the package into the 'Engine'.
|
||||
package.register_into_engine(&mut engine);
|
||||
```
|
69
rhai_engine/rhaibook/rust/packages/crate.md
Normal file
69
rhai_engine/rhaibook/rust/packages/crate.md
Normal file
@@ -0,0 +1,69 @@
|
||||
Create a Custom Package as an Independent Crate
|
||||
===============================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Creating a custom [package] as an independent crate allows it to be shared by multiple projects.
|
||||
|
||||
```admonish abstract.small "Key concepts"
|
||||
|
||||
* Create a Rust crate that specifies [`rhai`](https://crates.io/crates/rhai) as dependency.
|
||||
|
||||
* The main `lib.rs` module can contain the [package] being constructed.
|
||||
```
|
||||
|
||||
```admonish example.small
|
||||
The projects [`rhai-rand`] and [`rhai-sci`] show simple examples of creating a custom [package]
|
||||
as an independent crate.
|
||||
```
|
||||
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
`Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "my-package" # 'my-package' crate
|
||||
|
||||
[dependencies]
|
||||
rhai = "{{version}}" # assuming {{version}} is the latest version
|
||||
```
|
||||
|
||||
`lib.rs`:
|
||||
|
||||
```rust
|
||||
use rhai::def_package;
|
||||
use rhai::plugin::*;
|
||||
|
||||
// This is a plugin module
|
||||
#[export_module]
|
||||
mod my_module {
|
||||
// Constants are ignored when used as a package
|
||||
pub const MY_NUMBER: i64 = 42;
|
||||
|
||||
pub fn greet(name: &str) -> String {
|
||||
format!("hello, {}!", name)
|
||||
}
|
||||
pub fn get_num() -> i64 {
|
||||
42
|
||||
}
|
||||
|
||||
// This is a sub-module, but if using combine_with_exported_module!, it will
|
||||
// be flattened and all functions registered at the top level.
|
||||
pub mod my_sub_module {
|
||||
pub fn get_sub_num() -> i64 {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define the package 'MyPackage' which is exported for the crate.
|
||||
def_package! {
|
||||
/// My own personal super package in a new crate!
|
||||
pub MyPackage(module) {
|
||||
combine_with_exported_module!(module, "my-functions", my_module);
|
||||
}
|
||||
}
|
||||
```
|
256
rhai_engine/rhaibook/rust/packages/create.md
Normal file
256
rhai_engine/rhaibook/rust/packages/create.md
Normal file
@@ -0,0 +1,256 @@
|
||||
Create a Custom Package
|
||||
=======================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish info.side "See also"
|
||||
|
||||
See also the [_One Engine Instance Per Call_]({{rootUrl}}/patterns/parallel.md) pattern.
|
||||
```
|
||||
|
||||
The macro `def_package!` can be used to create a custom [package].
|
||||
|
||||
A custom [package] can aggregate many other [packages] into a single self-contained unit.
|
||||
More functions can be added on top of others.
|
||||
|
||||
Custom [packages] are extremely useful when multiple [raw `Engine`] instances must be created such
|
||||
that they all share the same set of functions.
|
||||
|
||||
|
||||
`def_package!`
|
||||
--------------
|
||||
|
||||
> ```rust
|
||||
> def_package! {
|
||||
> /// Package description doc-comment
|
||||
> pub name(variable) {
|
||||
> :
|
||||
> // package init code block
|
||||
> :
|
||||
> }
|
||||
>
|
||||
> // Multiple packages can be defined at the same time,
|
||||
> // possibly with base packages and/or code to setup an Engine.
|
||||
>
|
||||
> /// Package description doc-comment
|
||||
> pub(crate) name(variable) : base_package_1, base_package_2, ... {
|
||||
> :
|
||||
> // package init code block
|
||||
> :
|
||||
> } |> |engine| {
|
||||
> :
|
||||
> // engine setup code block
|
||||
> :
|
||||
> }
|
||||
>
|
||||
> /// A private package description doc-comment
|
||||
> name(variable) {
|
||||
> :
|
||||
> // private package init code block
|
||||
> :
|
||||
> }
|
||||
>
|
||||
> :
|
||||
> }
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Element | Description |
|
||||
| :---------------------: | ---------------------------------------------------------------------------------------------------- |
|
||||
| description | doc-comment for the [package] |
|
||||
| `pub` etc. | visibility of the [package] |
|
||||
| name | name of the [package], usually ending in ...`Package` |
|
||||
| variable | a variable name holding a reference to the [module] forming the [package], usually `module` or `lib` |
|
||||
| base_package | an external [package] type that is merged into this [package] as a dependency |
|
||||
| package init code block | a code block that initializes the [package] |
|
||||
| engine | a variable name holding a mutable reference to an [`Engine`] |
|
||||
| engine setup code block | a code block that performs setup tasks on an [`Engine`] during registration |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
// Import necessary types and traits.
|
||||
use rhai::def_package; // 'def_package!' macro
|
||||
use rhai::packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage};
|
||||
use rhai::{FuncRegistration, CustomType, TypeBuilder};
|
||||
|
||||
/// This is a custom type.
|
||||
#[derive(Clone, CustomType)]
|
||||
struct TestStruct {
|
||||
foo: String,
|
||||
bar: i64,
|
||||
baz: bool
|
||||
}
|
||||
|
||||
def_package! {
|
||||
/// My own personal super package
|
||||
// Aggregate other base packages (if any) simply by listing them after a colon.
|
||||
pub MyPackage(module) : ArithmeticPackage, LogicPackage, BasicArrayPackage, BasicMapPackage
|
||||
{
|
||||
// Register additional Rust function.
|
||||
FuncRegistration::new("get_bar_value")
|
||||
.with_params_info(&["s: &mut TestStruct", "i64"])
|
||||
.set_into_module(module, |s: &mut TestStruct| s.bar);
|
||||
|
||||
// Register a function for use as a custom operator.
|
||||
FuncRegistration::new("@")
|
||||
.with_namespace(FnNamespace::Global) // <- make it available globally.
|
||||
.set_into_module(module, |x: i64, y: i64| x * x + y * y);
|
||||
} |> |engine| {
|
||||
// This optional block performs tasks on an 'Engine' instance,
|
||||
// e.g. register custom types and/or custom operators/syntax.
|
||||
|
||||
// Register custom type.
|
||||
engine.build_type::<TestStruct>();
|
||||
|
||||
// Define a custom operator '@' with precedence of 160
|
||||
// (i.e. between +|- and *|/).
|
||||
engine.register_custom_operator("@", 160).unwrap();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: Feature gates on base packages"
|
||||
|
||||
Base packages in the list after the colon (`:`) can also have attributes (such as feature gates)!
|
||||
|
||||
```rust
|
||||
def_package! {
|
||||
// 'BasicArrayPackage' is used only under 'arrays' feature.
|
||||
pub MyPackage(module) :
|
||||
ArithmeticPackage,
|
||||
LogicPackage,
|
||||
#[cfg(feature = "arrays")]
|
||||
BasicArrayPackage
|
||||
{
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish danger.small "Advanced: `Engine` setup with `|>`"
|
||||
|
||||
A second code block (in the syntax of a [closure]) following a right-triangle symbol (`|>`)
|
||||
is run whenever the [package] is being registered.
|
||||
|
||||
It allows performing setup tasks directly on that [`Engine`], e.g. registering [custom types],
|
||||
[custom operators] and/or [custom syntax].
|
||||
|
||||
```rust
|
||||
def_package! {
|
||||
pub MyPackage(module) {
|
||||
:
|
||||
:
|
||||
} |> |engine| {
|
||||
// Call methods on 'engine'
|
||||
}
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Create a Custom Package from a Plugin Module
|
||||
--------------------------------------------
|
||||
|
||||
```admonish question.side "Trivia"
|
||||
|
||||
This is exactly how Rhai's built-in [packages], such as `BasicMathPackage`, are actually implemented.
|
||||
```
|
||||
|
||||
By far the easiest way to create a custom [package] is to call `plugin::combine_with_exported_module!`
|
||||
from within `def_package!` which simply merges in all the functions defined within a [plugin module].
|
||||
|
||||
Due to specific requirements of a [package], `plugin::combine_with_exported_module!`
|
||||
_flattens_ all sub-modules (i.e. all functions and [type iterators] defined within sub-modules
|
||||
are pulled up to the top level instead) and so there will not be any sub-modules added to the [package].
|
||||
|
||||
Variables in the [plugin module] are ignored.
|
||||
|
||||
```rust
|
||||
// Import necessary types and traits.
|
||||
use rhai::def_package;
|
||||
use rhai::packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage};
|
||||
use rhai::plugin::*;
|
||||
|
||||
// Define plugin module.
|
||||
#[export_module]
|
||||
mod my_plugin_module {
|
||||
// Custom type.
|
||||
pub type ABC = TestStruct;
|
||||
|
||||
// Public constant.
|
||||
pub const MY_NUMBER: i64 = 42;
|
||||
|
||||
// Public function.
|
||||
pub fn greet(name: &str) -> String {
|
||||
format!("hello, {}!", name)
|
||||
}
|
||||
|
||||
// Non-public functions are by default not exported.
|
||||
fn get_private_num() -> i64 {
|
||||
42
|
||||
}
|
||||
|
||||
// Public function.
|
||||
pub fn get_num() -> i64 {
|
||||
get_private_num()
|
||||
}
|
||||
|
||||
// Custom operator.
|
||||
#[rhai_fn(name = "@")]
|
||||
pub fn square_add(x: i64, y: i64) -> i64 {
|
||||
x * x + y * y
|
||||
}
|
||||
|
||||
// A sub-module. If using 'combine_with_exported_module!', however,
|
||||
// it will be flattened and all functions registered at the top level.
|
||||
//
|
||||
// Because of this flattening, sub-modules are very convenient for
|
||||
// putting feature gates onto large groups of functions.
|
||||
#[cfg(feature = "sub-num-feature")]
|
||||
pub mod my_sub_module {
|
||||
// Only available under 'sub-num-feature'.
|
||||
pub fn get_sub_num() -> i64 {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def_package! {
|
||||
/// My own personal super package
|
||||
// Aggregate other base packages (if any) simply by listing them after a colon.
|
||||
pub MyPackage(module) : ArithmeticPackage, LogicPackage, BasicArrayPackage, BasicMapPackage
|
||||
{
|
||||
// Merge all registered functions and constants from the plugin module
|
||||
// into the custom package.
|
||||
//
|
||||
// The sub-module 'my_sub_module' is flattened and its functions
|
||||
// registered at the top level.
|
||||
//
|
||||
// The text string name in the second parameter can be anything
|
||||
// and is reserved for future use; it is recommended to be an
|
||||
// ID string that uniquely identifies the plugin module.
|
||||
//
|
||||
// The constant variable, 'MY_NUMBER', is ignored.
|
||||
//
|
||||
// This call ends up registering three functions at the top level of
|
||||
// the package:
|
||||
// 1) 'greet'
|
||||
// 2) 'get_num'
|
||||
// 3) 'get_sub_num' (flattened from 'my_sub_module')
|
||||
//
|
||||
combine_with_exported_module!(module, "my-mod", my_plugin_module));
|
||||
} |> |engine| {
|
||||
// This optional block is used to set up an 'Engine' during registration.
|
||||
|
||||
// Define a custom operator '@' with precedence of 160
|
||||
// (i.e. between +|- and *|/).
|
||||
engine.register_custom_operator("@", 160).unwrap();
|
||||
}
|
||||
}
|
||||
```
|
60
rhai_engine/rhaibook/rust/packages/index.md
Normal file
60
rhai_engine/rhaibook/rust/packages/index.md
Normal file
@@ -0,0 +1,60 @@
|
||||
Packages
|
||||
========
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
The built-in library of Rhai is provided as various _packages_ that can be turned into _shared_
|
||||
[modules], which in turn can be registered into the _global namespace_ of an [`Engine`] via
|
||||
`Engine::register_global_module`.
|
||||
|
||||
Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be loaded in
|
||||
order for packages to be used.
|
||||
|
||||
```admonish question.small "Trivia: Packages _are_ modules!"
|
||||
|
||||
Internally, a _package_ is a [module], with some conveniences to make it easier to define and use as
|
||||
a standard _library_ for an [`Engine`].
|
||||
|
||||
Packages typically contain Rust functions that are callable within a Rhai script.
|
||||
All _top-level_ functions in a package are available under the _global namespace_
|
||||
(i.e. they're available without namespace qualifiers).
|
||||
|
||||
Sub-[modules] and [variables] are ignored in packages.
|
||||
```
|
||||
|
||||
|
||||
Share a Package Among Multiple `Engine`'s
|
||||
-----------------------------------------
|
||||
|
||||
`Engine::register_global_module` and `Engine::register_static_module` both require _shared_ [modules].
|
||||
|
||||
Once a package is created (e.g. via `Package::new`), it can be registered into multiple instances of
|
||||
[`Engine`], even across threads (under the [`sync`] feature).
|
||||
|
||||
```admonish tip.small "Tip: Sharing package"
|
||||
|
||||
A package only has to be created _once_ and essentially shared among multiple [`Engine`] instances.
|
||||
|
||||
This is particularly useful when spawning large number of [raw `Engine`'s][raw `Engine`].
|
||||
```
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
use rhai::packages::Package // load the 'Package' trait to use packages
|
||||
use rhai::packages::CorePackage; // the 'core' package contains basic functionalities (e.g. arithmetic)
|
||||
|
||||
// Create a package - can be shared among multiple 'Engine' instances
|
||||
let package = CorePackage::new();
|
||||
|
||||
let mut engines_collection: Vec<Engine> = Vec::new();
|
||||
|
||||
// Create 100 'raw' Engines
|
||||
for _ in 0..100 {
|
||||
let mut engine = Engine::new_raw();
|
||||
|
||||
// Register the package into the global namespace.
|
||||
package.register_into_engine(&mut engine);
|
||||
|
||||
engines_collection.push(engine);
|
||||
}
|
||||
```
|
40
rhai_engine/rhaibook/rust/print-custom.md
Normal file
40
rhai_engine/rhaibook/rust/print-custom.md
Normal file
@@ -0,0 +1,40 @@
|
||||
Printing for Custom Types
|
||||
=========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Provide These Functions
|
||||
-----------------------
|
||||
|
||||
To use [custom types] for [`print`] and [`debug`], or convert a [custom type] into a [string],
|
||||
it is necessary that the following functions, at minimum, be registered (assuming the [custom type]
|
||||
is `T: Display + Debug`).
|
||||
|
||||
| Function | Signature | Typical implementation | Usage |
|
||||
| ----------- | ------------------------------------ | ---------------------- | ---------------------------------------------------------- |
|
||||
| `to_string` | <code>\|x: &mut T\| -> String</code> | `x.to_string()` | converts the [custom type] into a [string] |
|
||||
| `to_debug` | <code>\|x: &mut T\| -> String</code> | `format!("{x:?}")` | converts the [custom type] into a [string] in debug format |
|
||||
|
||||
~~~admonish tip.small "Tip: `#[rhai_fn(global)]`"
|
||||
|
||||
If these functions are defined via a [plugin module], be sure to include the `#[rhai_fn(global)]` attribute
|
||||
in order to make them available globally.
|
||||
|
||||
See [this section]({{rootUrl}}/plugins/module.md#use-rhai_fnglobal) for more details.
|
||||
~~~
|
||||
|
||||
|
||||
Also Consider These
|
||||
-------------------
|
||||
|
||||
The following functions are implemented using `to_string` or `to_debug` by default, but can be
|
||||
overloaded with custom versions.
|
||||
|
||||
| Function | Signature | Default | Usage |
|
||||
| ------------- | ---------------------------------------------- | ----------- | ---------------------------------------------------------------------- |
|
||||
| `print` | <code>\|x: &mut T\| -> String</code> | `to_string` | converts the [custom type] into a [string] for the [`print`] statement |
|
||||
| `debug` | <code>\|x: &mut T\| -> String</code> | `to_debug` | converts the [custom type] into a [string] for the [`debug`] statement |
|
||||
| `+` operator | <code>\|s: &str, x: T\| -> String</code> | `to_string` | concatenates the [custom type] with another [string] |
|
||||
| `+` operator | <code>\|x: &mut T, s: &str\| -> String</code> | `to_string` | concatenates another [string] with the [custom type] |
|
||||
| `+=` operator | <code>\|s: &mut ImmutableString, x: T\|</code> | `to_string` | appends the [custom type] to an existing [string] |
|
105
rhai_engine/rhaibook/rust/reg-custom-type.md
Normal file
105
rhai_engine/rhaibook/rust/reg-custom-type.md
Normal file
@@ -0,0 +1,105 @@
|
||||
Manually Register Custom Type
|
||||
=============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
```admonish warning.small "Warning"
|
||||
|
||||
This assumes that the type is defined in an external crate and so the [`CustomType`] trait
|
||||
cannot be implemented for it due to Rust's [_orphan rule_](https://doc.rust-lang.org/book/ch10-02-traits.html).
|
||||
```
|
||||
|
||||
```admonish tip.side "Tip: Working with enums"
|
||||
|
||||
It is also possible to use Rust enums with Rhai.
|
||||
|
||||
See the pattern [Working with Enums]({{rootUrl}}/patterns/enums.md) for more details.
|
||||
```
|
||||
|
||||
The custom type needs to be _registered_ into an [`Engine`] via:
|
||||
|
||||
| `Engine` API | `type_of` output |
|
||||
| ------------------------------ | ------------------- |
|
||||
| `register_type::<T>` | full Rust path name |
|
||||
| `register_type_with_name::<T>` | friendly name |
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct {
|
||||
field: i64
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn new() -> Self {
|
||||
Self { field: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register custom type with friendly name
|
||||
engine.register_type_with_name::<TestStruct>("TestStruct")
|
||||
.register_fn("new_ts", TestStruct::new);
|
||||
|
||||
// Cast result back to custom type.
|
||||
let result = engine.eval::<TestStruct>(
|
||||
"
|
||||
new_ts() // calls 'TestStruct::new'
|
||||
")?;
|
||||
|
||||
println!("result: {}", result.field); // prints 1
|
||||
|
||||
```
|
||||
|
||||
`type_of()` a Custom Type
|
||||
-------------------------
|
||||
|
||||
```admonish question.side.wide "Giving types the same name?"
|
||||
|
||||
It is OK to register several custom types under the _same_ friendly name
|
||||
and `type_of()` will faithfully return it.
|
||||
|
||||
How this might possibly be useful is left to the imagination of the user.
|
||||
```
|
||||
|
||||
[`type_of()`] works fine with custom types and returns the name of the type.
|
||||
|
||||
If `Engine::register_type_with_name` is used to register the custom type with a special
|
||||
"pretty-print" friendly name, [`type_of()`] will return that name instead.
|
||||
|
||||
```rust
|
||||
engine.register_type::<TestStruct1>()
|
||||
.register_fn("new_ts1", TestStruct1::new)
|
||||
.register_type_with_name::<TestStruct2>("TestStruct")
|
||||
.register_fn("new_ts2", TestStruct2::new);
|
||||
|
||||
let ts1_type = engine.eval::<String>("let x = new_ts1(); x.type_of()")?;
|
||||
let ts2_type = engine.eval::<String>("let x = new_ts2(); x.type_of()")?;
|
||||
|
||||
println!("{ts1_type}"); // prints 'path::to::TestStruct'
|
||||
println!("{ts2_type}"); // prints 'TestStruct'
|
||||
```
|
||||
|
||||
|
||||
`==` Operator
|
||||
-------------
|
||||
|
||||
Many standard functions (e.g. filtering, searching and sorting) expect a custom type to be
|
||||
_comparable_, meaning that the `==` operator must be registered for the custom type.
|
||||
|
||||
For example, in order to use the [`in`] operator with a custom type for an [array],
|
||||
the `==` operator is used to check whether two values are the same.
|
||||
|
||||
```rust
|
||||
// Assume 'TestStruct' implements `PartialEq`
|
||||
engine.register_fn("==",
|
||||
|item1: &mut TestStruct, item2: TestStruct| item1 == &item2
|
||||
);
|
||||
|
||||
// Then this works in Rhai:
|
||||
let item = new_ts(); // construct a new 'TestStruct'
|
||||
item in array; // 'in' operator uses '=='
|
||||
```
|
193
rhai_engine/rhaibook/rust/register-raw.md
Normal file
193
rhai_engine/rhaibook/rust/register-raw.md
Normal file
@@ -0,0 +1,193 @@
|
||||
Use the Low-Level API to Register a Rust Function
|
||||
=================================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
When a native Rust function is registered with an [`Engine`] using the `register_XXX` API, Rhai
|
||||
transparently converts all function arguments from [`Dynamic`] into the correct types before calling
|
||||
the function.
|
||||
|
||||
For more power and flexibility, there is a _low-level_ API to work directly with [`Dynamic`] values
|
||||
without the conversions.
|
||||
|
||||
|
||||
Raw Function Registration
|
||||
-------------------------
|
||||
|
||||
The `Engine::register_raw_fn` method is marked _volatile_, meaning that it may be changed without warning.
|
||||
|
||||
If this is acceptable, then using this method to register a Rust function opens up more opportunities.
|
||||
|
||||
```rust
|
||||
engine.register_raw_fn(
|
||||
"increment_by", // function name
|
||||
&[ // a slice containing parameter types
|
||||
std::any::TypeId::of::<i64>(), // type of first parameter
|
||||
std::any::TypeId::of::<i64>() // type of second parameter
|
||||
],
|
||||
|context, args| { // fixed function signature
|
||||
// Arguments are guaranteed to be correct in number and of the correct types.
|
||||
|
||||
// But remember this is Rust, so you can keep only one mutable reference at any one time!
|
||||
// Therefore, get a '&mut' reference to the first argument _last_.
|
||||
// Alternatively, use `args.split_first_mut()` etc. to split the slice first.
|
||||
|
||||
let y = *args[1].read_lock::<i64>().unwrap(); // get a reference to the second argument
|
||||
// then copy it because it is a primary type
|
||||
|
||||
let y = args[1].take().cast::<i64>(); // alternatively, directly 'consume' it
|
||||
|
||||
let y = args[1].as_int().unwrap(); // alternatively, use 'as_xxx()'
|
||||
|
||||
let x = args[0].write_lock::<i64>().unwrap(); // get a '&mut' reference to the first argument
|
||||
|
||||
*x += y; // perform the action
|
||||
|
||||
Ok(Dynamic::UNIT) // must be 'Result<Dynamic, Box<EvalAltResult>>'
|
||||
}
|
||||
);
|
||||
|
||||
// The above is the same as (in fact, internally they are equivalent):
|
||||
|
||||
engine.register_fn("increment_by", |x: &mut i64, y: i64| *x += y);
|
||||
```
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
The function signature passed to `Engine::register_raw_fn` takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :-------------------: | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `T` | `impl Clone` | return type of the function |
|
||||
| `context` | [`NativeCallContext`] | the current _native call context_, useful for recursively calling functions on the same [`Engine`] |
|
||||
| `args` | `&mut [&mut Dynamic]` | a slice containing `&mut` references to [`Dynamic`] values.<br/>The slice is guaranteed to contain enough arguments _of the correct types_. |
|
||||
|
||||
### Return value
|
||||
|
||||
The return value is the result of the function call.
|
||||
|
||||
Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned).
|
||||
Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations
|
||||
will be on the cloned copy.
|
||||
|
||||
|
||||
Extract The First `&mut` Argument (If Any)
|
||||
------------------------------------------
|
||||
|
||||
To extract the first `&mut` argument passed by reference from the `args` parameter (`&mut [&mut Dynamic]`),
|
||||
use the following to get a mutable reference to the underlying value:
|
||||
|
||||
```rust
|
||||
let value: &mut T = &mut *args[0].write_lock::<T>().unwrap();
|
||||
|
||||
*value = ... // overwrite the existing value of the first `&mut` parameter
|
||||
```
|
||||
|
||||
When there is a mutable reference to the first `&mut` argument, there can be no other immutable
|
||||
references to `args`, otherwise the Rust borrow checker will complain.
|
||||
|
||||
Therefore, always extract the mutable reference last, _after_ all other arguments are taken.
|
||||
|
||||
|
||||
Extract Other Pass-By-Value Arguments
|
||||
-------------------------------------
|
||||
|
||||
To extract an argument passed by value from the `args` parameter (`&mut [&mut Dynamic]`), use the following statements.
|
||||
|
||||
| Argument type | Access statement (`n` = argument position) | Result | Original value |
|
||||
| ------------------------- | ---------------------------------------------- | :-------------------------------------: | :------------: |
|
||||
| `INT` | `args[n].as_int().unwrap()` | `INT` | untouched |
|
||||
| `FLOAT` | `args[n].as_float().unwrap()` | `FLOAT` | untouched |
|
||||
| [`Decimal`][rust_decimal] | `args[n].as_decimal().unwrap()` | [`Decimal`][rust_decimal] | untouched |
|
||||
| `bool` | `args[n].as_bool().unwrap()` | `bool` | untouched |
|
||||
| `char` | `args[n].as_char().unwrap()` | `char` | untouched |
|
||||
| `()` | `args[n].as_unit().unwrap()` | `()` | untouched |
|
||||
| [String] | `&*args[n].as_immutable_string_ref().unwrap()` | [`&ImmutableString`][`ImmutableString`] | untouched |
|
||||
| [String] (consumed) | `args[n].take().cast::<ImmutableString>()` | [`ImmutableString`] | [`()`] |
|
||||
| Others | `&*args[n].read_lock::<T>().unwrap()` | `&T` | untouched |
|
||||
| Others (consumed) | `args[n].take().cast::<T>()` | `T` | [`()`] |
|
||||
|
||||
|
||||
Example – Pass a Callback to a Rust Function
|
||||
--------------------------------------------------
|
||||
|
||||
The low-level API is useful when there is a need to interact with the scripting [`Engine`]
|
||||
within a function.
|
||||
|
||||
The following example registers a function that takes a [function pointer] as an argument,
|
||||
then calls it within the same [`Engine`]. This way, a _callback_ function can be provided
|
||||
to a native Rust function.
|
||||
|
||||
The example also showcases the use of `FnPtr::call_raw`, a low-level API which allows binding the
|
||||
`this` pointer to the function pointer call.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, FnPtr};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a Rust function
|
||||
engine.register_raw_fn(
|
||||
"bar",
|
||||
&[
|
||||
std::any::TypeId::of::<i64>(), // parameter types
|
||||
std::any::TypeId::of::<FnPtr>(),
|
||||
std::any::TypeId::of::<i64>(),
|
||||
],
|
||||
|context, args| {
|
||||
// 'args' is guaranteed to contain enough arguments of the correct types
|
||||
|
||||
let fp = args[1].take().cast::<FnPtr>(); // 2nd argument - function pointer
|
||||
let value = args[2].take(); // 3rd argument - function argument
|
||||
|
||||
// The 1st argument holds the 'this' pointer.
|
||||
// This should be done last as it gets a mutable reference to 'args'.
|
||||
let this_ptr = args.get_mut(0).unwrap();
|
||||
|
||||
// Use 'FnPtr::call_raw' to call the function pointer with the context
|
||||
// while also binding the 'this' pointer!
|
||||
fp.call_raw(&context, Some(this_ptr), [value])
|
||||
},
|
||||
);
|
||||
|
||||
let result = engine.eval::<i64>(
|
||||
r#"
|
||||
fn foo(x) { this += x; } // script-defined function 'foo'
|
||||
|
||||
let x = 41; // object
|
||||
x.bar(foo, 1); // pass 'foo' as function pointer
|
||||
x
|
||||
"#)?;
|
||||
```
|
||||
|
||||
~~~admonish tip "Tip: Hold multiple references"
|
||||
|
||||
In order to access a value argument that is expensive to clone _while_ holding a mutable reference
|
||||
to the first argument, use one of the following tactics:
|
||||
|
||||
1. If it is a [primary type][standard types] other than [string], use `as_xxx()` as above;
|
||||
|
||||
2. Directly _consume_ that argument via `arg[i].take()` as above.
|
||||
|
||||
3. Use `split_first_mut` to partition the slice:
|
||||
|
||||
```rust
|
||||
// Partition the slice
|
||||
let (first, rest) = args.split_first_mut().unwrap();
|
||||
|
||||
// Mutable reference to the first parameter, of type '&mut A'
|
||||
let this_ptr = &mut *first.write_lock::<A>().unwrap();
|
||||
|
||||
// Immutable reference to the second value parameter, of type '&B'
|
||||
// This can be mutable but there is no point because the parameter is passed by value
|
||||
let value_ref = &*rest[0].read_lock::<B>().unwrap();
|
||||
```
|
||||
~~~
|
164
rhai_engine/rhaibook/rust/serde.md
Normal file
164
rhai_engine/rhaibook/rust/serde.md
Normal file
@@ -0,0 +1,164 @@
|
||||
Serialization and Deserialization of `Dynamic` with `serde`
|
||||
===========================================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai's [`Dynamic`] type supports serialization and deserialization by
|
||||
[`serde`](https://crates.io/crates/serde) via the [`serde`][features] feature.
|
||||
|
||||
```admonish tip.small
|
||||
|
||||
[`Dynamic`] works _both_ as a _serialization format_ as well as a data type that is serializable.
|
||||
```
|
||||
|
||||
[`serde`]: https://crates.io/crates/serde
|
||||
[`serde_json`]: https://crates.io/crates/serde_json
|
||||
[`serde::Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
|
||||
[`serde::Deserialize`]: https://docs.serde.rs/serde/trait.Deserialize.html
|
||||
|
||||
|
||||
Serialize/Deserialize a `Dynamic`
|
||||
---------------------------------
|
||||
|
||||
With the [`serde`][features] feature turned on, [`Dynamic`] implements [`serde::Serialize`] and
|
||||
[`serde::Deserialize`], so it can easily be serialized and deserialized with [`serde`] (for example,
|
||||
to and from JSON via [`serde_json`]).
|
||||
|
||||
```rust
|
||||
let value: Dynamic = ...;
|
||||
|
||||
// Serialize 'Dynamic' to JSON
|
||||
let json = serde_json::to_string(&value);
|
||||
|
||||
// Deserialize 'Dynamic' from JSON
|
||||
let result: Dynamic = serde_json::from_str(&json);
|
||||
```
|
||||
|
||||
```admonish note.small "Custom types"
|
||||
|
||||
[Custom types] are serialized as text strings of the value's type name.
|
||||
```
|
||||
|
||||
```admonish warning.small "BLOB's"
|
||||
|
||||
[BLOB's], or byte-arrays, are serialized and deserialized as simple [arrays] for some formats such as JSON.
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Lighter alternative for JSON"
|
||||
|
||||
The [`serde_json`] crate is quite heavy.
|
||||
|
||||
If only _simple_ JSON parsing (i.e. only deserialization) of a hash object into a Rhai [object map] is required,
|
||||
the [`Engine::parse_json`]({{rootUrl}}/language/json.md}}) method is available as a _cheap_ alternative,
|
||||
but it does not provide the same level of correctness, nor are there any configurable options.
|
||||
```
|
||||
|
||||
|
||||
`Dynamic` as Serialization Format
|
||||
---------------------------------
|
||||
|
||||
A [`Dynamic`] can be seamlessly converted to and from any type that implements [`serde::Serialize`]
|
||||
and/or [`serde::Deserialize`], acting as a serialization format.
|
||||
|
||||
### Serialize Any Type to `Dynamic`
|
||||
|
||||
The function `rhai::serde::to_dynamic` automatically converts any Rust type that implements
|
||||
[`serde::Serialize`] into a [`Dynamic`].
|
||||
|
||||
For primary types, this is usually not necessary because using [`Dynamic::from`][`Dynamic`] is much
|
||||
easier and is essentially the same thing. The only difference is treatment for integer values.
|
||||
`Dynamic::from` keeps different integer types intact, while `rhai::serde::to_dynamic` converts them
|
||||
all into [`INT`][standard types] (i.e. the system integer type which is `i64` or `i32` depending on
|
||||
the [`only_i32`] feature).
|
||||
|
||||
Rust `struct`'s (or any type that is marked as a `serde` map) are converted into [object maps] while
|
||||
Rust `Vec`'s (or any type that is marked as a `serde` sequence) are converted into [arrays].
|
||||
|
||||
While it is also simple to serialize a Rust type to `JSON` via `serde`,
|
||||
then use [`Engine::parse_json`]({{rootUrl}}/language/json.md) to convert it into an [object map],
|
||||
`rhai::serde::to_dynamic` serializes it to [`Dynamic`] directly via `serde` without going through the `JSON` step.
|
||||
|
||||
```rust
|
||||
use rhai::{Dynamic, Map};
|
||||
use rhai::serde::to_dynamic;
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct MyStruct {
|
||||
a: i64,
|
||||
b: Vec<String>,
|
||||
c: bool,
|
||||
d: Point
|
||||
}
|
||||
|
||||
let x = MyStruct {
|
||||
a: 42,
|
||||
b: vec![ "hello".into(), "world".into() ],
|
||||
c: true,
|
||||
d: Point { x: 123.456, y: 999.0 }
|
||||
};
|
||||
|
||||
// Convert the 'MyStruct' into a 'Dynamic'
|
||||
let map: Dynamic = to_dynamic(x);
|
||||
|
||||
map.is::<Map>() == true;
|
||||
```
|
||||
|
||||
### Deserialize a `Dynamic` into Any Type
|
||||
|
||||
The function `rhai::serde::from_dynamic` automatically converts a [`Dynamic`] value into any Rust type
|
||||
that implements [`serde::Deserialize`].
|
||||
|
||||
In particular, [object maps] are converted into Rust `struct`'s (or any type that is marked as
|
||||
a `serde` map) while [arrays] are converted into Rust `Vec`'s (or any type that is marked
|
||||
as a `serde` sequence).
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Dynamic};
|
||||
use rhai::serde::from_dynamic;
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct MyStruct {
|
||||
a: i64,
|
||||
b: Vec<String>,
|
||||
c: bool,
|
||||
d: Point
|
||||
}
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
let result: Dynamic = engine.eval(
|
||||
r#"
|
||||
#{
|
||||
a: 42,
|
||||
b: [ "hello", "world" ],
|
||||
c: true,
|
||||
d: #{ x: 123.456, y: 999.0 }
|
||||
}
|
||||
"#)?;
|
||||
|
||||
// Convert the 'Dynamic' object map into 'MyStruct'
|
||||
let x: MyStruct = from_dynamic(&result)?;
|
||||
```
|
||||
|
||||
```admonish warning.small "Cannot deserialize shared values"
|
||||
|
||||
A [`Dynamic`] containing a _shared_ value cannot be deserialized.
|
||||
It will give a type error.
|
||||
|
||||
Use `Dynamic::flatten` to obtain a cloned copy before deserialization
|
||||
(if the value is not shared, it is simply returned and not cloned).
|
||||
|
||||
Shared values are turned off via the [`no_closure`] feature.
|
||||
```
|
17
rhai_engine/rhaibook/rust/strings-interner.md
Normal file
17
rhai_engine/rhaibook/rust/strings-interner.md
Normal file
@@ -0,0 +1,17 @@
|
||||
Strings Interner
|
||||
================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Because [strings] are immutable (i.e. the use the type [`ImmutableString`] instead of normal Rust `String`),
|
||||
each operation on a [string] actually creates a new [`ImmutableString`] instance.
|
||||
|
||||
A _strings interner_ can substantially reduce memory usage by reusing the same [`ImmutableString`]
|
||||
instance for the same [string] content.
|
||||
|
||||
An [`Engine`] contains a strings interner which is enabled by default
|
||||
(disabled when using a [raw `Engine`]).
|
||||
|
||||
The maximum number of [strings] to be interned can be set via
|
||||
[`Engine::set_max_strings_interned`][options] (set to zero to disable the strings interner).
|
74
rhai_engine/rhaibook/rust/strings.md
Normal file
74
rhai_engine/rhaibook/rust/strings.md
Normal file
@@ -0,0 +1,74 @@
|
||||
`String` Parameters in Rust Functions
|
||||
=====================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
~~~admonish danger "Warning: Avoid `String` parameters"
|
||||
|
||||
As much as possible, avoid using `String` parameters in functions.
|
||||
|
||||
Each `String` argument is cloned during _every_ single call to that function –
|
||||
and the copy immediately thrown away right after the call.
|
||||
|
||||
Needless to say, it is _extremely_ inefficient to use `String` parameters.
|
||||
~~~
|
||||
|
||||
|
||||
`&str` Maps to `ImmutableString`
|
||||
--------------------------------
|
||||
|
||||
```admonish warning.side "Common mistake"
|
||||
|
||||
A common mistake made by novice Rhai users is to register functions with `String` parameters.
|
||||
```
|
||||
|
||||
Rust functions accepting parameters of `String` should use `&str` instead because it maps directly
|
||||
to [`ImmutableString`] which is the type that Rhai uses to represent [strings] internally.
|
||||
|
||||
The parameter type `String` involves always converting an [`ImmutableString`] into a `String`
|
||||
which mandates cloning it.
|
||||
|
||||
Using [`ImmutableString`] or `&str` is much more efficient.
|
||||
|
||||
```rust
|
||||
fn get_len1(s: String) -> i64 { // BAD!!! Very inefficient!!!
|
||||
s.len() as i64
|
||||
}
|
||||
fn get_len2(s: &str) -> i64 { // this is better
|
||||
s.len() as i64
|
||||
}
|
||||
fn get_len3(s: ImmutableString) -> i64 { // the above is equivalent to this
|
||||
s.len() as i64
|
||||
}
|
||||
|
||||
engine.register_fn("len1", get_len1)
|
||||
.register_fn("len2", get_len2)
|
||||
.register_fn("len3", get_len3);
|
||||
|
||||
let len = engine.eval::<i64>("len1(x)")?; // 'x' cloned, very inefficient!!!
|
||||
let len = engine.eval::<i64>("len2(x)")?; // 'x' is shared
|
||||
let len = engine.eval::<i64>("len3(x)")?; // 'x' is shared
|
||||
```
|
||||
|
||||
|
||||
~~~admonish danger "`&mut String` does not work – use `&mut ImmutableString` instead"
|
||||
|
||||
A function with the first parameter being `&mut String` does not match a string argument passed to it,
|
||||
which has type `ImmutableString`.
|
||||
|
||||
In fact, `&mut String` is treated as an opaque [custom type].
|
||||
|
||||
```rust
|
||||
fn bad(s: &mut String) { ... } // '&mut String' will not match string values
|
||||
|
||||
fn good(s: &mut ImmutableString) { ... }
|
||||
|
||||
engine.register_fn("bad", bad)
|
||||
.register_fn("good", good);
|
||||
|
||||
engine.eval(r#"bad("hello")"#)?; // <- error: function 'bad (string)' not found
|
||||
|
||||
engine.eval(r#"good("hello")"#)?; // <- this one works
|
||||
```
|
||||
~~~
|
15
rhai_engine/rhaibook/rust/traits.md
Normal file
15
rhai_engine/rhaibook/rust/traits.md
Normal file
@@ -0,0 +1,15 @@
|
||||
Traits
|
||||
======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
A number of traits, under the `rhai::` module namespace, provide additional functionalities.
|
||||
|
||||
| Trait | Description | Methods |
|
||||
| ------------------------ | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
|
||||
| `CustomType` | trait to build a [custom type] for use with an [`Engine`] | `build` |
|
||||
| `Func` | trait for creating Rust closures from script | `create_from_ast`, `create_from_script` |
|
||||
| `FuncArgs` | trait for parsing function call arguments | `parse` |
|
||||
| `ModuleResolver` | trait implemented by [module resolution][module resolver] services | `resolve`, `resolve_ast`, `resolve_raw` |
|
||||
| `packages::Package` | trait implemented by [packages] | `init`, `init_engine`, `register_into_engine`, `register_into_engine_as`, `as_shared_module` |
|
||||
| `plugin::PluginFunction` | trait implemented by [plugin] functions | `call`, `is_method_call`, `has_context`, `is_pure` |
|
Reference in New Issue
Block a user