diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b1d8e2..ffcc3516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,23 +11,44 @@ Bug fixes * `import` statements inside `eval` no longer cause errors in subsequent code. * Functions marked `global` in `import`ed modules with no alias names now work properly. +Speed Improvements +------------------ + +* Due to a code refactor, built-in operators for standard types now run even faster, in certain cases by 20-30%. + New features ------------ ### Stable hashing -* It is now possible to specify a fixed _seed_ for use with the `ahash` hasher, via an environment variable, in order to force stable (i.e. deterministic) hashes for function signatures. This is necessary when using Rhai across shared-library boundaries. -* A build script is now used to extract the environment variable (`RHAI_AHASH_SEED`) and splice it into the source code before compilation. +* It is now possible to specify a fixed _seed_ for use with the `ahash` hasher, via an environment variable, in order to force stable (i.e. deterministic) hashes for function signatures. +* This is necessary when using Rhai across shared-library boundaries. +* A build script is used to extract the environment variable (`RHAI_AHASH_SEED`) and splice it into the source code before compilation. + +### Serializable `Scope` + +* `Scope` is now serializable and deserializable via `serde`. + +### Call native Rust functions in `NativeCallContext` + +* `NativeCallContext::call_native_fn` is added to call registered native Rust functions only. +* `NativeCallContext::call_native_fn_raw` is added as the advanced version. +* This is often desirable as Rust functions typically do not want a similar-named scripted function to hijack the process -- which will cause brittleness. + +### Custom syntax improvements + +* The look-ahead symbol for custom syntax now renders a string literal in quotes (instead of the generic term `string`). +* This facilitates more accurate parsing by separating strings and identifiers. + +### Limits API + +* Methods returning maximum limits (e.g. `Engine::max_string_len`) are now available even under `unchecked`. +* This helps avoid the proliferation of unnecessary feature flags in third-party library code. Enhancements ------------ -* The look-ahead symbol for custom syntax now renders a string literal in quotes (instead of the generic term `string`). This facilitates more accurate parsing by separating strings and identifiers. -* Due to a code refactor, built-in operators for standard types now run even faster, in certain cases by 20-30%. -* `Scope` is now serializable and deserializable via `serde`. -* `Scope` now contains a const generic parameter that allows specifying how many entries to be kept inline. * `parse_json` function is added to parse a JSON string into an object map. -* Methods returning maximum limits (e.g. `Engine::max_string_len`) are now available even under `unchecked` in order to avoid unnecessary feature flags in third-party library code. Version 1.10.1 diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index ac1fb7f3..3b0a7615 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -233,6 +233,7 @@ impl Engine { arg_values, ) } + /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. fn _call_fn( &self, diff --git a/src/func/native.rs b/src/func/native.rs index 490fba30..34cd6ec3 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -135,7 +135,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> impl<'a> NativeCallContext<'a> { /// _(internals)_ Create a new [`NativeCallContext`]. - /// Exported under the `metadata` feature only. + /// Exported under the `internals` feature only. #[deprecated( since = "1.3.0", note = "`NativeCallContext::new` will be moved under `internals`. Use `FnPtr::call` to call a function pointer directly." @@ -274,7 +274,7 @@ impl<'a> NativeCallContext<'a> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let result = self.call_fn_raw(fn_name, false, false, &mut args)?; + let result = self._call_fn_raw(fn_name, false, false, false, &mut args)?; let typ = self.engine().map_type_name(result.type_name()); @@ -283,7 +283,32 @@ impl<'a> NativeCallContext<'a> { ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() }) } - /// Call a function inside the call context. + /// Call a registered native Rust function inside the call context with the provided arguments. + /// + /// This is often useful because Rust functions typically only want to cross-call other + /// registered Rust functions and not have to worry about scripted functions hijacking the + /// process unknowingly (or deliberately). + #[inline] + pub fn call_native_fn( + &self, + fn_name: impl AsRef, + args: impl FuncArgs, + ) -> RhaiResultOf { + let mut arg_values = StaticVec::new_const(); + args.parse(&mut arg_values); + + let mut args: StaticVec<_> = arg_values.iter_mut().collect(); + + let result = self._call_fn_raw(fn_name, true, false, false, &mut args)?; + + let typ = self.engine().map_type_name(result.type_name()); + + result.try_cast().ok_or_else(|| { + let t = self.engine().map_type_name(type_name::()).into(); + ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() + }) + } + /// Call a function (native Rust or scripted) inside the call context. /// /// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for /// a script-defined function (or the object of a method call). @@ -302,6 +327,7 @@ impl<'a> NativeCallContext<'a> { /// /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is /// not consumed. + #[inline(always)] pub fn call_fn_raw( &self, fn_name: impl AsRef, @@ -309,15 +335,76 @@ impl<'a> NativeCallContext<'a> { is_method_call: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { - let mut global = self + self._call_fn_raw(fn_name, false, is_ref_mut, is_method_call, args) + } + /// Call a registered native Rust function inside the call context. + /// + /// This is often useful because Rust functions typically only want to cross-call other + /// registered Rust functions and not have to worry about scripted functions hijacking the + /// process unknowingly (or deliberately). + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. + /// + /// # Arguments + /// + /// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid + /// unnecessarily cloning the arguments. + /// + /// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them + /// _before_ calling this function. + /// + /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is + /// not consumed. + #[inline(always)] + pub fn call_native_fn_raw( + &self, + fn_name: impl AsRef, + is_ref_mut: bool, + args: &mut [&mut Dynamic], + ) -> RhaiResult { + self._call_fn_raw(fn_name, true, is_ref_mut, false, args) + } + + /// Call a function (native Rust or scripted) inside the call context. + fn _call_fn_raw( + &self, + fn_name: impl AsRef, + native_only: bool, + is_ref_mut: bool, + is_method_call: bool, + args: &mut [&mut Dynamic], + ) -> RhaiResult { + let global = &mut self .global .cloned() .unwrap_or_else(|| GlobalRuntimeState::new(self.engine())); - let mut caches = Caches::new(); + let caches = &mut Caches::new(); let fn_name = fn_name.as_ref(); let args_len = args.len(); + if native_only { + return self + .engine() + .call_native_fn( + global, + caches, + self.lib, + fn_name, + calc_fn_hash(None, fn_name, args_len), + args, + is_ref_mut, + false, + Position::NONE, + self.level + 1, + ) + .map(|(r, ..)| r); + } + + // Native or script + let hash = if is_method_call { FnCallHashes::from_all( #[cfg(not(feature = "no_function"))] @@ -331,8 +418,8 @@ impl<'a> NativeCallContext<'a> { self.engine() .exec_fn_call( None, - &mut global, - &mut caches, + global, + caches, self.lib, fn_name, hash, diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 7e639275..2c5bd716 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -835,7 +835,7 @@ pub mod array_functions { for item in array { if ctx - .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()]) + .call_native_fn_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => { if item.type_id() == value.type_id() { @@ -927,7 +927,7 @@ pub mod array_functions { for (i, item) in array.iter_mut().enumerate().skip(start) { if ctx - .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()]) + .call_native_fn_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => { if item.type_id() == value.type_id() { @@ -2313,7 +2313,7 @@ pub mod array_functions { for (a1, a2) in array1.iter_mut().zip(array2.iter_mut()) { if !ctx - .call_fn_raw(OP_EQUALS, true, false, &mut [a1, a2]) + .call_native_fn_raw(OP_EQUALS, true, &mut [a1, a2]) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => { if a1.type_id() == a2.type_id() { diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 3360be62..e5174c1b 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -213,7 +213,7 @@ mod map_functions { for (m1, v1) in map1 { if let Some(v2) = map2.get_mut(m1) { let equals = ctx - .call_fn_raw(OP_EQUALS, true, false, &mut [v1, v2])? + .call_native_fn_raw(OP_EQUALS, true, &mut [v1, v2])? .as_bool() .unwrap_or(false); diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index d8c3845a..a0763ada 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -38,7 +38,7 @@ pub fn print_with_func( ctx: &NativeCallContext, value: &mut Dynamic, ) -> crate::ImmutableString { - match ctx.call_fn_raw(fn_name, true, false, &mut [value]) { + match ctx.call_native_fn_raw(fn_name, true, &mut [value]) { Ok(result) if result.is::() => { result.into_immutable_string().expect("`ImmutableString`") }