Skip to content

Commit

Permalink
Merge pull request #156 from schungx/master
Browse files Browse the repository at this point in the history
Immutable strings and built-in common operators
  • Loading branch information
schungx committed Jun 1, 2020
2 parents e5584b2 + c6e5f67 commit b2f7c50
Show file tree
Hide file tree
Showing 54 changed files with 3,783 additions and 2,289 deletions.
20 changes: 8 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rhai"
version = "0.14.1"
version = "0.15.0"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust"
Expand All @@ -20,26 +20,22 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
num-traits = { version = "0.2.11", default-features = false }

[features]
#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_module", "no_float", "only_i32", "unchecked", "no_optimize", "sync"]
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
default = []
unchecked = [] # unchecked arithmetic
no_index = [] # no arrays and indexing
no_float = [] # no floating-point
no_function = [] # no script-defined functions
no_object = [] # no custom objects
sync = [] # restrict to only types that implement Send + Sync
no_optimize = [] # no script optimizer
no_module = [] # no modules
no_float = [] # no floating-point
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
sync = [] # restrict to only types that implement Send + Sync
no_index = [] # no arrays and indexing
no_object = [] # no custom objects
no_function = [] # no script-defined functions
no_module = [] # no modules

# compiling for no-std
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]

# other developer features
no_stdlib = [] # do not register the standard library
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing

[profile.release]
lto = "fat"
codegen-units = 1
Expand Down
763 changes: 483 additions & 280 deletions README.md

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,70 @@
Rhai Release Notes
==================

Version 0.14.2
==============

Regression fix
--------------

* Do not optimize script with `eval_expression` - it is assumed to be one-off and short.

Bug fixes
---------

* Indexing with an index or dot expression now works property (it compiled wrongly before).
For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of causing a runtime error.

Breaking changes
----------------

* `Engine::compile_XXX` functions now return `ParseError` instead of `Box<ParseError>`.
* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns
`Result<Dynamic, Box<EvalAltResult>>`.
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even
under `Engine::new_raw`.
* Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`.
This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters
should switch to `rhai::ImmutableString` (which is either `Rc<String>` or `Arc<String>` depending on
whether the `sync` feature is used).
* Native Rust functions registered with the `Engine` also mutates the first argument when called in
normal function-call style (previously the first argument will be passed by _value_ if not called
in method-call style). Of course, if the first argument is a calculated value (e.g. result of an
expression), then mutating it has no effect, but at least it is not cloned.
* Some built-in methods (e.g. `len` for string, `floor` for `FLOAT`) now have _property_ versions in
addition to methods to simplify coding.

New features
------------

* Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
* New `EvalPackage` to disable `eval`.
* `Module::set_getter_fn`, `Module::set_setter_fn` and `Module:set_indexer_fn` to register getter/setter/indexer functions.
* `Engine::call_fn_dynamic` for more control in calling script functions.

Speed enhancements
------------------

* Common operators (e.g. `+`, `>`, `==`) now call into highly efficient built-in implementations for standard types
(i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
This yields a 5-10% speed benefit depending on script operator usage. Scripts running tight loops will see
significant speed-up.
* Common assignment operators (e.g. `+=`, `%=`) now call into highly efficient built-in implementations for
standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage`
(and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
* Operator-assignment statements (e.g. `+=`) are now handled directly and much faster.
* Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning.
* For Native Rust functions taking a first `&mut` parameter, the first argument is passed by reference instead of
by value, even if not called in method-call style. This allows many functions declared with `&mut` parameter to avoid
excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result
in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference,
avoiding the cloning altogether.
* A custom hasher simply passes through `u64` keys without hashing to avoid function call hash keys
(which are by themselves `u64`) being hashed twice.


Version 0.14.1
==============

Expand Down
2 changes: 1 addition & 1 deletion benches/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn bench_engine_new_raw_core(bench: &mut Bencher) {

#[bench]
fn bench_engine_register_fn(bench: &mut Bencher) {
fn hello(a: INT, b: Array, c: Map) -> bool {
fn hello(_a: INT, _b: Array, _c: Map) -> bool {
true
}

Expand Down
24 changes: 24 additions & 0 deletions benches/eval_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,27 @@ fn bench_eval_array_large_set(bench: &mut Bencher) {

bench.iter(|| engine.consume_ast(&ast).unwrap());
}

#[bench]
fn bench_eval_array_loop(bench: &mut Bencher) {
let script = r#"
let list = [];
for i in range(0, 10_000) {
list.push(i);
}
let sum = 0;
for i in list {
sum += i;
}
"#;

let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);

let ast = engine.compile(script).unwrap();

bench.iter(|| engine.consume_ast(&ast).unwrap());
}
115 changes: 115 additions & 0 deletions benches/eval_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,118 @@ fn bench_eval_expression_number_operators(bench: &mut Bencher) {

bench.iter(|| engine.consume_ast(&ast).unwrap());
}

#[bench]
fn bench_eval_expression_optimized_simple(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;

let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Simple);
let ast = engine.compile_expression(script).unwrap();

bench.iter(|| engine.consume_ast(&ast).unwrap());
}

#[bench]
fn bench_eval_expression_optimized_full(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;

let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Full);
let ast = engine.compile_expression(script).unwrap();

bench.iter(|| engine.consume_ast(&ast).unwrap());
}

#[bench]
fn bench_eval_call_expression(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;

let engine = Engine::new();

bench.iter(|| engine.eval_expression::<bool>(script).unwrap());
}

#[bench]
fn bench_eval_call(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;

let engine = Engine::new();

bench.iter(|| engine.eval::<bool>(script).unwrap());
}

#[bench]
fn bench_eval_loop_number(bench: &mut Bencher) {
let script = r#"
let s = 0;
for x in range(0, 10000) {
s += 1;
}
"#;

let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);

let ast = engine.compile(script).unwrap();

bench.iter(|| engine.consume_ast(&ast).unwrap());
}

#[bench]
fn bench_eval_loop_strings_build(bench: &mut Bencher) {
let script = r#"
let s = 0;
for x in range(0, 10000) {
s += "x";
}
"#;

let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);

let ast = engine.compile(script).unwrap();

bench.iter(|| engine.consume_ast(&ast).unwrap());
}

#[bench]
fn bench_eval_loop_strings_no_build(bench: &mut Bencher) {
let script = r#"
let s = "hello";
for x in range(0, 10000) {
s += "";
}
"#;

let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);

let ast = engine.compile(script).unwrap();

bench.iter(|| engine.consume_ast(&ast).unwrap());
}
2 changes: 1 addition & 1 deletion benches/iterations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn bench_iterations_1000(bench: &mut Bencher) {
let x = 1_000;
while x > 0 {
x = x - 1;
x -= 1;
}
"#;

Expand Down
36 changes: 34 additions & 2 deletions benches/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ fn bench_parse_full(bench: &mut Bencher) {
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len() <= #{prop:name}.len() &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;

Expand Down Expand Up @@ -94,11 +94,43 @@ fn bench_parse_primes(bench: &mut Bencher) {
}
print("Total " + total_primes_found + " primes.");
print("Run time = " + now.elapsed() + " seconds.");
print("Run time = " + now.elapsed + " seconds.");
"#;

let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);

bench.iter(|| engine.compile(script).unwrap());
}

#[bench]
fn bench_parse_optimize_simple(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;

let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Simple);

bench.iter(|| engine.compile_expression(script).unwrap());
}

#[bench]
fn bench_parse_optimize_full(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;

let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Full);

bench.iter(|| engine.compile_expression(script).unwrap());
}
2 changes: 1 addition & 1 deletion scripts/fibonacci.rhai
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let now = timestamp();

let result = fib(target);

print("Finished. Run time = " + now.elapsed() + " seconds.");
print("Finished. Run time = " + now.elapsed + " seconds.");

print("Fibonacci number #" + target + " = " + result);

Expand Down
22 changes: 22 additions & 0 deletions scripts/for2.rhai
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const MAX = 1_000_000;

print("Iterating an array with " + MAX + " items...");

print("Ready... Go!");

let now = timestamp();

let list = [];

for i in range(0, MAX) {
list.push(i);
}

let sum = 0;

for i in list {
sum += i;
}

print("Sum = " + sum);
print("Finished. Run time = " + now.elapsed + " seconds.");
2 changes: 1 addition & 1 deletion scripts/loop.rhai
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let x = 10;
loop {
print(x);

x = x - 1;
x -= 1;

if x <= 0 { break; }
}

0 comments on commit b2f7c50

Please sign in to comment.