Merge pull request #207 from schungx/master

Fix currying in object map closure method calls.
This commit is contained in:
Stephen Chung 2020-08-04 18:58:35 +08:00 committed by GitHub
commit dab85c9070
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 68 additions and 51 deletions

View File

@ -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).

View File

@ -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

View File

@ -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`]

View File

@ -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.

View File

@ -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 |

View File

@ -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);

View File

@ -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::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>() {
None

View File

@ -705,12 +705,25 @@ impl Engine {
#[cfg(not(feature = "no_object"))]
if let Some(map) = obj.read_lock::<Map>() {
if let Some(val) = map.get(_fn_name) {
if let Some(f) = val.read_lock::<FnPtr>() {
if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
// 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())
};
}
}
};

View File

@ -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<FN, ARGS, RET> {