diff --git a/README.md b/README.md index 7aef538c..338a1309 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Standard features * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * [Function overloading](https://schungx.github.io/rhai/language/overload.html). * [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). -* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html). +* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html) and [closures](https://schungx.github.io/rhai/language/fn-closure.html). * Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). diff --git a/RELEASES.md b/RELEASES.md index 1d2a842b..7e72c56b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -6,10 +6,10 @@ Version 0.18.0 This version adds: -* Binding the `this` pointer in a function pointer `call`. * Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions. * Currying of function pointers. * Closures - auto-currying of anonymous functions to capture shared variables from the external scope. +* Binding the `this` pointer in a function pointer `call`. * Capturing call scope via `func!(...)` syntax. New features diff --git a/doc/src/start/builds/minimal.md b/doc/src/start/builds/minimal.md index 46b85007..71ed7bd9 100644 --- a/doc/src/start/builds/minimal.md +++ b/doc/src/start/builds/minimal.md @@ -38,8 +38,8 @@ Omitting arrays ([`no_index`]) yields the most code-size savings, followed by fl ([`no_float`]), checked arithmetic/script resource limits ([`unchecked`]) and finally object maps and custom types ([`no_object`]). Where the usage scenario does not call for loading externally-defined modules, use [`no_module`] to save some bytes. -Disable script-defined functions ([`no_function`]) when the feature is not needed. -Both of these have little code size savings. +Disable script-defined functions ([`no_function`]) and possibly closures ([`no_closure`]) when the features are not needed. +Both of these have some code size savings but not much. Use a Raw [`Engine`] diff --git a/doc/src/start/builds/performance.md b/doc/src/start/builds/performance.md index 27747208..a0c65fe5 100644 --- a/doc/src/start/builds/performance.md +++ b/doc/src/start/builds/performance.md @@ -3,11 +3,12 @@ Performance Build {{#include ../../links.md}} +Some features are for performance. For example, using [`only_i32`] or [`only_i64`] disables all other integer types (such as `u16`). + + Use Only One Integer Type ------------------------ -Some features are for performance. For example, using [`only_i32`] or [`only_i64`] disables all other integer types (such as `u16`). - If only a single integer type is needed in scripts - most of the time this is the case - it is best to avoid registering lots of functions related to other integer types that will never be used. As a result, [`Engine`] creation will be faster because fewer functions need to be loaded. @@ -22,8 +23,8 @@ On 64-bit targets this may not gain much, but on some 32-bit targets this improv requiring more CPU cycles to complete. -Minimize Size of [`Dynamic`] ---------------------------- +Minimize Size of `Dynamic` +------------------------- Turning on [`no_float`], and [`only_i32`] makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`. @@ -42,3 +43,15 @@ It is cheap to clone, but expensive to modify (a new copy of the string must be Therefore, functions taking `String` parameters should use `ImmutableString` or `&str` (which maps to `ImmutableString`) for the best performance with Rhai. + + +Disable Closures +---------------- + +Support for [closures], including capturing shared values, adds material overhead to script evaluation. + +This is because every data access must be checked whether it is a shared value, and if so, take a read +or write lock before reading it. + +Use [`no_closure`] to disable closure and capturing support and optimize the hot path +because there is no need to take locks for shared data. diff --git a/doc/src/start/examples/scripts.md b/doc/src/start/examples/scripts.md index 57c38d13..fff32275 100644 --- a/doc/src/start/examples/scripts.md +++ b/doc/src/start/examples/scripts.md @@ -20,7 +20,7 @@ There are also a number of examples scripts that showcase Rhai's features, all i | [`function_decl3.rhai`]({{repoTree}}/scripts/function_decl3.rhai) | A [function] with many parameters | | [`if1.rhai`]({{repoTree}}/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example | | [`loop.rhai`]({{repoTree}}/scripts/loop.rhai) | Count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop | -| [`oop.rhai`]({{repoTree}}/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] | +| [`oop.rhai`]({{repoTree}}/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] with [closures] | | [`op1.rhai`]({{repoTree}}/scripts/op1.rhai) | Just simple addition | | [`op2.rhai`]({{repoTree}}/scripts/op2.rhai) | Simple addition and multiplication | | [`op3.rhai`]({{repoTree}}/scripts/op3.rhai) | Change evaluation order with parenthesis | diff --git a/scripts/oop.rhai b/scripts/oop.rhai index fe03b636..e90d690b 100644 --- a/scripts/oop.rhai +++ b/scripts/oop.rhai @@ -1,45 +1,41 @@ -// This script simulates object-oriented programming (OOP) techniques -// using function pointers (Fn) and object maps. +// This script simulates object-oriented programming (OOP) techniques using closures. + +// External variable that will be captured. +let last_value = (); // Define object let obj1 = #{ - _data: 42, // data field - get_data: Fn("getData"), // property getter - action: Fn("action"), // method - update: Fn("update1") // property setter + _data: 42, // data field + get_data: || this._data, // property getter + action: || print("Data=" + this._data), // method + update: |x| { // property setter + this._data = x; + last_value = this._data; // capture 'last_value' + this.action(); + } }; -fn getData() { - this._data -} -fn action() { - print("Data=" + this._data); -} -fn update1(x) { - this._data = x; - this.action(); -} - -if obj1.get_data() > 0 { // property access - obj1.update(123); // call method +if obj1.get_data() > 0 { // property access + obj1.update(123); // call method } else { print("we have a problem here"); } // Define another object based on the first object let obj2 = #{ - _data: 0, // data field - new value - update: Fn("update2") // property setter - another function + _data: 0, // data field - new value + update: |x| { // property setter - another function + this._data = x * 2; + last_value = this._data; // capture 'last_value' + this.action(); + } }; -obj2.fill_with(obj1); // add all other fields from obj1 +obj2.fill_with(obj1); // add all other fields from obj1 -fn update2(x) { - this._data = x * 2; - this.action(); -} - -if obj2.get_data() > 0 { // property access - obj2.update(0); // call method +if obj2.get_data() > 0 { // property access + print("we have another problem here"); } else { - obj2.update(42); // call method + obj2.update(42); // call method } + +print("Should be 84: " + last_value); diff --git a/src/any.rs b/src/any.rs index 5efcad16..aa218127 100644 --- a/src/any.rs +++ b/src/any.rs @@ -600,6 +600,7 @@ impl Dynamic { /// # Panics /// /// Panics under the `no_closure` feature. + #[inline(always)] pub fn into_shared(self) -> Self { #[cfg(not(feature = "no_closure"))] return match self.0 { @@ -833,7 +834,6 @@ impl Dynamic { let data = cell.read().unwrap(); let type_id = (*data).type_id(); - println!("Type = {}", (*data).type_name()); if type_id != TypeId::of::() && TypeId::of::() != TypeId::of::() { None diff --git a/src/fn_call.rs b/src/fn_call.rs index d56487e2..c7d9050c 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -705,12 +705,25 @@ impl Engine { #[cfg(not(feature = "no_object"))] if let Some(map) = obj.read_lock::() { if let Some(val) = map.get(_fn_name) { - if let Some(f) = val.read_lock::() { + if let Some(fn_ptr) = val.read_lock::() { // Remap the function name - redirected = f.get_fn_name().clone(); + redirected = fn_ptr.get_fn_name().clone(); _fn_name = &redirected; - // Recalculate the hash based on the new function name - _hash = calc_fn_hash(empty(), _fn_name, idx.len(), empty()); + // Add curried arguments + if !fn_ptr.curry().is_empty() { + fn_ptr + .curry() + .iter() + .cloned() + .enumerate() + .for_each(|(i, v)| idx.insert(i, v)); + } + // Recalculate the hash based on the new function name and new arguments + _hash = if native { + 0 + } else { + calc_fn_hash(empty(), _fn_name, idx.len(), empty()) + }; } } }; diff --git a/src/fn_register.rs b/src/fn_register.rs index e01f44a1..5c519d4c 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -2,7 +2,7 @@ #![allow(non_snake_case)] -use crate::any::{Dynamic, Variant, DynamicWriteLock}; +use crate::any::{Dynamic, DynamicWriteLock, Variant}; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::module::Module; @@ -11,12 +11,7 @@ use crate::r#unsafe::unsafe_cast_box; use crate::result::EvalAltResult; use crate::utils::ImmutableString; -use crate::stdlib::{ - any::TypeId, - boxed::Box, - mem, - string::String, -}; +use crate::stdlib::{any::TypeId, boxed::Box, mem, string::String}; /// Trait to register custom functions with the `Engine`. pub trait RegisterFn {