Correct speed claim and others in docs.

This commit is contained in:
Stephen Chung 2020-12-19 17:46:34 +08:00
parent d73f3a1d60
commit 0182b2d3f4
7 changed files with 80 additions and 24 deletions

View File

@ -30,6 +30,7 @@ New features
------------
* `AST::iter_functions` now returns `ScriptFnMetadata` which includes, among others, _doc-comments_ for functions prefixed by `///` or `/**`.
* A functions lookup cache is added to make function call resolution faster.
Enhancements
------------

View File

@ -27,7 +27,6 @@ Fast
* Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
An unofficial Fibonacci benchmark puts Rhai somewhere between Wren and Python.
* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations.

View File

@ -27,8 +27,8 @@ It doesn't attempt to be a new language. For example:
There is, however, support for simulated [closures] via [currying] a [function pointer] with
captured shared variables.
* **No byte-codes/JIT** - Rhai has an optimized AST-walking interpreter which is fast enough for most usage scenarios.
Essential AST data structures are packed and kept together to maximize cache friendliness.
* **No byte-codes/JIT** - Rhai has an optimized AST-walking interpreter which is fast enough for most casual
usage scenarios. Essential AST data structures are packed and kept together to maximize cache friendliness.
Functions are dispatched based on pre-calculated hashes and accessing variables are mostly through pre-calculated
offsets to the variables file (a [`Scope`]), so it is seldom necessary to look something up by text name.

View File

@ -52,7 +52,7 @@ the return value, are [`Dynamic`] the types are simply not shown.
A script-defined function always takes dynamic arguments, and the return type is also dynamic:
> `foo(x, y, z)`
> `foo(x, y, z) -> Dynamic`
probably defined as:
@ -71,17 +71,17 @@ is the same as:
Functions defined in [plugin modules] are the best. They contain all the metadata
describing the functions.
A plugin function `merge`:
For example, a plugin function `merge`:
> `merge(list: &mut MyStruct<i64>, num: usize, name: &str) -> Option<bool>`
Notice that function names do not need to be valid identifiers.
An operator defined as a [fallible function] in a [plugin module] via `#[rhai_fn(name="+=", return_raw)]`
returns `Result<bool, Box<EvalAltResult>>`:
For example, an operator defined as a [fallible function] in a [plugin module] via
`#[rhai_fn(name="+=", return_raw)]` returns `Result<bool, Box<EvalAltResult>>`:
> `+=(list: &mut MyStruct<i64>, num: usize, name: &str) -> Result<bool, Box<EvalAltResult>>`
A [property getter][getters/setters] defined in a [plugin module]:
For example, a [property getter][getters/setters] defined in a [plugin module]:
> `get$prop(obj: &mut MyStruct<i64>) -> String`

View File

@ -178,22 +178,21 @@ Use `switch` Through Arrays
---------------------------
Another way to work with Rust enums in a `switch` expression is through exposing the internal data
of each enum variant as a variable-length [array], usually with the name of the variant as
the first item for convenience:
(or at least those that act as effective _discriminants_) of each enum variant as a variable-length
[array], usually with the name of the variant as the first item for convenience:
```rust
use rhai::Array;
engine.register_get("enum_data", |x: &mut Enum| {
match x {
Enum::Foo => vec![
"Foo".into()
] as Array,
Enum::Foo => vec![ "Foo".into() ] as Array,
Enum::Bar(value) => vec![
"Bar".into(), (*value).into()
] as Array,
// Say, skip the data field because it is not
// used as a discriminant
Enum::Bar(value) => vec![ "Bar".into() ] as Array,
// Say, all fields act as discriminants
Enum::Baz(val1, val2) => vec![
"Baz".into(), val1.clone().into(), (*val2).into()
] as Array
@ -208,8 +207,7 @@ Then it is a simple matter to match an enum via the `switch` expression:
// 'enum_data' creates a variable-length array with 'MyEnum' data
let x = switch value.enum_data {
["Foo"] => 1,
["Bar", 42] => 2,
["Bar", 123] => 3,
["Bar"] => value.field_1,
["Baz", "hello", false] => 4,
["Baz", "hello", true] => 5,
_ => 9
@ -220,10 +218,20 @@ x == 5;
// Which is essentially the same as:
let x = switch [value.type, value.field_0, value.field_1] {
["Foo", (), ()] => 1,
["Bar", 42, ()] => 2,
["Bar", 123, ()] => 3,
["Bar", 42, ()] => 42,
["Bar", 123, ()] => 123,
:
["Baz", "hello", false] => 4,
["Baz", "hello", true] => 5,
_ => 9
}
```
Usually, a helper method returns an array of values that can uniquely determine
the switch case based on actual usage requirements - which means that it probably
skips fields that contain data instead of discriminants.
Then `switch` is used to very quickly match through a large number of array shapes
and jump to the appropriate case implementation.
Data fields can then be extracted from the enum independently.

View File

@ -26,10 +26,10 @@ Use Closures to Define Methods
-----------------------------
[Anonymous functions] or [closures] defined as values for [object map] properties take on
a syntactic shape that resembles very closely that of class methods in an OOP language.
a syntactic shape which resembles very closely that of class methods in an OOP language.
Closures also _[capture][automatic currying]_ variables from the defining environment, which is a very
common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and
common language feature. Capturing is accomplished via a feature called _[automatic currying]_ and
can be turned off via the [`no_closure`] feature.
@ -59,3 +59,51 @@ factor = 2;
obj.update(42);
obj.action(); // prints 84
```
Simulating Inheritance With Mixin
--------------------------------
The `fill_with` method of [object maps] can be conveniently used to _polyfill_ default
method implementations from a _base class_, as per OOP lingo.
Do not use the `mixin` method because it _overwrites_ existing fields.
```rust
// Define base class
let BaseClass = #{
factor: 1,
data: 42,
get_data: || this.data * 2,
update: |x| this.data += x * this.factor
};
let obj = #{
// Override base class field
factor: 100,
// Override base class method
// Notice that the base class can also be accessed, if in scope
get_data: || this.call(BaseClass.get_data) * 999,
}
// Polyfill missing fields/methods
obj.fill_with(BaseClass);
// By this point, 'obj' has the following:
//
// #{
// factor: 100
// data: 42,
// get_data: || this.call(BaseClass.get_data) * 999,
// update: |x| this.data += x * this.factor
// }
// obj.get_data() => (this.data (42) * 2) * 999
obj.get_data() == 83916;
obj.update(1);
obj.data == 142
```

View File

@ -649,7 +649,7 @@ impl Engine {
state: &mut State,
lib: &[&Module],
script: &str,
pos: Position,
_pos: Position,
_level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state)?;
@ -663,7 +663,7 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))]
if _level > self.max_call_levels() {
return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos)));
return Err(Box::new(EvalAltResult::ErrorStackOverflow(_pos)));
}
// Compile the script text