Add call_native_fn for context.

This commit is contained in:
Stephen Chung 2022-10-14 16:57:14 +08:00
parent 71e475be13
commit ea63c66cf0
6 changed files with 128 additions and 19 deletions

View File

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

View File

@ -233,6 +233,7 @@ impl Engine {
arg_values,
)
}
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
fn _call_fn(
&self,

View File

@ -135,7 +135,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + '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<T: Variant + Clone>(
&self,
fn_name: impl AsRef<str>,
args: impl FuncArgs,
) -> RhaiResultOf<T> {
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::<T>()).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<str>,
@ -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<str>,
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<str>,
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,

View File

@ -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() {

View File

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

View File

@ -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::<crate::ImmutableString>() => {
result.into_immutable_string().expect("`ImmutableString`")
}