Avoid cloning in array methods as much as possible.

This commit is contained in:
Stephen Chung 2021-10-19 19:57:15 +08:00
parent 9d1ab2c8a1
commit 2334cd8f56
7 changed files with 296 additions and 192 deletions

View File

@ -4,6 +4,12 @@ Rhai Release Notes
Version 1.2.0 Version 1.2.0
============= =============
Enhancements
------------
* `NativeCallContext::call_fn_dynamic_raw` is deprecated and `NativeCallContext::call_fn_raw` is added.
* Array methods now avoid cloning as much as possible (although most predicates will involve cloning anyway if passed a closure).
Version 1.1.0 Version 1.1.0
============= =============

View File

@ -1,6 +1,8 @@
//! Module containing all deprecated API that will be removed in the next major version. //! Module containing all deprecated API that will be removed in the next major version.
use crate::{Dynamic, Engine, EvalAltResult, ImmutableString, Scope, AST}; use crate::{
Dynamic, Engine, EvalAltResult, ImmutableString, NativeCallContext, RhaiResult, Scope, AST,
};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -141,3 +143,28 @@ impl Dynamic {
self.into_immutable_string() self.into_immutable_string()
} }
} }
impl NativeCallContext<'_> {
/// Call a function inside the call context.
///
/// # WARNING
///
/// All arguments may be _consumed_, meaning that they may be replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
///
/// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function.
///
/// If `is_method` is [`true`], the first argument is assumed to be passed
/// by reference and is not consumed.
#[deprecated(since = "1.2.0", note = "use `call_fn_raw` instead")]
#[inline(always)]
pub fn call_fn_dynamic_raw(
&self,
fn_name: impl AsRef<str>,
is_method_call: bool,
args: &mut [&mut Dynamic],
) -> RhaiResult {
self.call_fn_raw(fn_name.as_ref(), is_method_call, is_method_call, args)
}
}

View File

@ -191,48 +191,42 @@ impl<'a> NativeCallContext<'a> {
/// Do not use the arguments after this call. If they are needed afterwards, /// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function. /// clone them _before_ calling this function.
/// ///
/// If `is_method` is [`true`], the first argument is assumed to be passed /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed
/// by reference and is not consumed. /// by reference and is not consumed.
#[inline(always)] ///
pub fn call_fn_dynamic_raw( /// 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).
pub fn call_fn_raw(
&self, &self,
fn_name: impl AsRef<str>, fn_name: &str,
is_ref_mut: bool,
is_method_call: bool, is_method_call: bool,
args: &mut [&mut Dynamic], args: &mut [&mut Dynamic],
) -> RhaiResult { ) -> Result<Dynamic, Box<EvalAltResult>> {
fn call_fn_dynamic_inner( let hash = if is_method_call {
context: &NativeCallContext, FnCallHashes::from_script_and_native(
is_method_call: bool, calc_fn_hash(fn_name, args.len() - 1),
fn_name: &str, calc_fn_hash(fn_name, args.len()),
args: &mut [&mut Dynamic], )
) -> Result<Dynamic, Box<EvalAltResult>> { } else {
let hash = if is_method_call { FnCallHashes::from_script(calc_fn_hash(fn_name, args.len()))
FnCallHashes::from_script_and_native( };
calc_fn_hash(fn_name, args.len() - 1),
calc_fn_hash(fn_name, args.len()),
)
} else {
FnCallHashes::from_script(calc_fn_hash(fn_name, args.len()))
};
context
.engine()
.exec_fn_call(
&mut context.mods.cloned().unwrap_or_default(),
&mut Default::default(),
context.lib,
fn_name,
hash,
args,
is_method_call,
is_method_call,
Position::NONE,
None,
0,
)
.map(|(r, _)| r)
}
call_fn_dynamic_inner(self, is_method_call, fn_name.as_ref(), args) self.engine()
.exec_fn_call(
&mut self.mods.cloned().unwrap_or_default(),
&mut Default::default(),
self.lib,
fn_name,
hash,
args,
is_ref_mut,
is_method_call,
Position::NONE,
None,
0,
)
.map(|(r, _)| r)
} }
} }

View File

@ -130,7 +130,7 @@ impl FnPtr {
} }
args.extend(arg_values.iter_mut()); args.extend(arg_values.iter_mut());
ctx.call_fn_dynamic_raw(self.fn_name(), is_method, &mut args) ctx.call_fn_raw(self.fn_name(), is_method, is_method, &mut args)
} }
} }

View File

@ -246,31 +246,37 @@ mod array_functions {
mapper: FnPtr, mapper: FnPtr,
) -> Result<Array, Box<EvalAltResult>> { ) -> Result<Array, Box<EvalAltResult>> {
if array.is_empty() { if array.is_empty() {
return Ok(array.clone()); return Ok(Array::new());
} }
let fn_name = mapper.fn_name();
let mut ar = Array::with_capacity(array.len()); let mut ar = Array::with_capacity(array.len());
let mut index_val = Dynamic::UNIT;
for (i, item) in array.iter_mut().enumerate() {
let mut args = [item, &mut index_val];
for (i, item) in array.iter().enumerate() {
ar.push( ar.push(
mapper match ctx.call_fn_raw(fn_name, true, false, &mut args[..1]) {
.call_dynamic(&ctx, None, [item.clone()]) Ok(r) => r,
.or_else(|err| match *err { Err(err) => match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _) EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(mapper.fn_name()) => if fn_sig.starts_with(fn_name) =>
{ {
mapper.call_dynamic(&ctx, None, [item.clone(), (i as INT).into()]) *args[1] = Dynamic::from(i as INT);
ctx.call_fn_raw(fn_name, true, false, &mut args)?
} }
_ => Err(err), _ => {
}) return EvalAltResult::ErrorInFunctionCall(
.map_err(|err| { "drain".to_string(),
Box::new(EvalAltResult::ErrorInFunctionCall( ctx.source().unwrap_or("").to_string(),
"map".to_string(), err,
ctx.source().unwrap_or("").to_string(), Position::NONE,
err, )
Position::NONE, .into()
)) }
})?, },
},
); );
} }
@ -286,31 +292,38 @@ mod array_functions {
return Ok(array.clone()); return Ok(array.clone());
} }
let fn_name = filter.fn_name();
let mut ar = Array::new(); let mut ar = Array::new();
let mut index_val = Dynamic::UNIT;
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter_mut().enumerate() {
if filter let mut args = [item, &mut index_val];
.call_dynamic(&ctx, None, [item.clone()])
.or_else(|err| match *err { let keep = match ctx.call_fn_raw(fn_name, true, false, &mut args[..1]) {
Ok(r) => r,
Err(err) => match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _) EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) => if fn_sig.starts_with(fn_name) =>
{ {
filter.call_dynamic(&ctx, None, [item.clone(), (i as INT).into()]) *args[1] = Dynamic::from(i as INT);
ctx.call_fn_raw(fn_name, true, false, &mut args)?
} }
_ => Err(err), _ => {
}) return EvalAltResult::ErrorInFunctionCall(
.map_err(|err| { "drain".to_string(),
Box::new(EvalAltResult::ErrorInFunctionCall( ctx.source().unwrap_or("").to_string(),
"filter".to_string(), err,
ctx.source().unwrap_or("").to_string(), Position::NONE,
err, )
Position::NONE, .into()
)) }
})? },
.as_bool() }
.unwrap_or(false) .as_bool()
{ .unwrap_or(false);
ar.push(item.clone());
if keep {
ar.push(args[0].clone());
} }
} }
@ -328,7 +341,7 @@ mod array_functions {
for item in array.iter_mut() { for item in array.iter_mut() {
if ctx if ctx
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()])
.or_else(|err| match *err { .or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if fn_sig.starts_with(OP_EQUALS) => if fn_sig.starts_with(OP_EQUALS) =>
@ -387,7 +400,7 @@ mod array_functions {
for (i, item) in array.iter_mut().enumerate().skip(start) { for (i, item) in array.iter_mut().enumerate().skip(start) {
if ctx if ctx
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()])
.or_else(|err| match *err { .or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if fn_sig.starts_with(OP_EQUALS) => if fn_sig.starts_with(OP_EQUALS) =>
@ -444,28 +457,36 @@ mod array_functions {
start as usize start as usize
}; };
for (i, item) in array.iter().enumerate().skip(start) { let fn_name = filter.fn_name();
if filter let mut index_val = Dynamic::UNIT;
.call_dynamic(&ctx, None, [item.clone()])
.or_else(|err| match *err { for (i, item) in array.iter_mut().enumerate().skip(start) {
let mut args = [item, &mut index_val];
let found = match ctx.call_fn_raw(fn_name, true, false, &mut args[..1]) {
Ok(r) => r,
Err(err) => match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _) EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) => if fn_sig.starts_with(fn_name) =>
{ {
filter.call_dynamic(&ctx, None, [item.clone(), (i as INT).into()]) *args[1] = Dynamic::from(i as INT);
ctx.call_fn_raw(fn_name, true, false, &mut args)?
} }
_ => Err(err), _ => {
}) return EvalAltResult::ErrorInFunctionCall(
.map_err(|err| { "drain".to_string(),
Box::new(EvalAltResult::ErrorInFunctionCall( ctx.source().unwrap_or("").to_string(),
"index_of".to_string(), err,
ctx.source().unwrap_or("").to_string(), Position::NONE,
err, )
Position::NONE, .into()
)) }
})? },
.as_bool() }
.unwrap_or(false) .as_bool()
{ .unwrap_or(false);
if found {
return Ok(i as INT); return Ok(i as INT);
} }
} }
@ -482,28 +503,36 @@ mod array_functions {
return Ok(false); return Ok(false);
} }
for (i, item) in array.iter().enumerate() { let fn_name = filter.fn_name();
if filter let mut index_val = Dynamic::UNIT;
.call_dynamic(&ctx, None, [item.clone()])
.or_else(|err| match *err { for (i, item) in array.iter_mut().enumerate() {
let mut args = [item, &mut index_val];
let found = match ctx.call_fn_raw(fn_name, true, false, &mut args[..1]) {
Ok(r) => r,
Err(err) => match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _) EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) => if fn_sig.starts_with(fn_name) =>
{ {
filter.call_dynamic(&ctx, None, [item.clone(), (i as INT).into()]) *args[1] = Dynamic::from(i as INT);
ctx.call_fn_raw(fn_name, true, false, &mut args)?
} }
_ => Err(err), _ => {
}) return EvalAltResult::ErrorInFunctionCall(
.map_err(|err| { "drain".to_string(),
Box::new(EvalAltResult::ErrorInFunctionCall( ctx.source().unwrap_or("").to_string(),
"some".to_string(), err,
ctx.source().unwrap_or("").to_string(), Position::NONE,
err, )
Position::NONE, .into()
)) }
})? },
.as_bool() }
.unwrap_or(false) .as_bool()
{ .unwrap_or(false);
if found {
return Ok(true); return Ok(true);
} }
} }
@ -520,28 +549,36 @@ mod array_functions {
return Ok(true); return Ok(true);
} }
for (i, item) in array.iter().enumerate() { let fn_name = filter.fn_name();
if !filter let mut index_val = Dynamic::UNIT;
.call_dynamic(&ctx, None, [item.clone()])
.or_else(|err| match *err { for (i, item) in array.iter_mut().enumerate() {
let mut args = [item, &mut index_val];
let found = match ctx.call_fn_raw(fn_name, true, false, &mut args[..1]) {
Ok(r) => r,
Err(err) => match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _) EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) => if fn_sig.starts_with(fn_name) =>
{ {
filter.call_dynamic(&ctx, None, [item.clone(), (i as INT).into()]) *args[1] = Dynamic::from(i as INT);
ctx.call_fn_raw(fn_name, true, false, &mut args)?
} }
_ => Err(err), _ => {
}) return EvalAltResult::ErrorInFunctionCall(
.map_err(|err| { "drain".to_string(),
Box::new(EvalAltResult::ErrorInFunctionCall( ctx.source().unwrap_or("").to_string(),
"all".to_string(), err,
ctx.source().unwrap_or("").to_string(), Position::NONE,
err, )
Position::NONE, .into()
)) }
})? },
.as_bool() }
.unwrap_or(false) .as_bool()
{ .unwrap_or(false);
if !found {
return Ok(false); return Ok(false);
} }
} }
@ -726,42 +763,62 @@ mod array_functions {
return Ok(Array::new()); return Ok(Array::new());
} }
let mut drained = Array::with_capacity(array.len()); let fn_name = filter.fn_name();
let mut index_val = Dynamic::UNIT;
let mut removed = Vec::with_capacity(array.len());
let mut count = 0;
let mut i = 0; for (i, item) in array.iter_mut().enumerate() {
let mut x = 0; let mut args = [item, &mut index_val];
while x < array.len() { let remove = match ctx.call_fn_raw(fn_name, true, false, &mut args[..1]) {
if filter Ok(r) => r,
.call_dynamic(&ctx, None, [array[x].clone()]) Err(err) => match *err {
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _) EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) => if fn_sig.starts_with(fn_name) =>
{ {
filter.call_dynamic(&ctx, None, [array[x].clone(), (i as INT).into()]) *args[1] = Dynamic::from(i as INT);
ctx.call_fn_raw(fn_name, true, false, &mut args)?
} }
_ => Err(err), _ => {
}) return EvalAltResult::ErrorInFunctionCall(
.map_err(|err| { "drain".to_string(),
Box::new(EvalAltResult::ErrorInFunctionCall( ctx.source().unwrap_or("").to_string(),
"drain".to_string(), err,
ctx.source().unwrap_or("").to_string(), Position::NONE,
err, )
Position::NONE, .into()
)) }
})? },
.as_bool()
.unwrap_or(false)
{
drained.push(array.remove(x));
} else {
x += 1;
} }
.as_bool()
.unwrap_or(false);
i += 1; removed.push(remove);
if remove {
count += 1;
}
} }
Ok(drained) if count == 0 {
return Ok(Array::new());
}
let mut result = Vec::with_capacity(count);
let mut x = 0;
let mut i = 0;
while i < array.len() {
if removed[x] {
result.push(array.remove(i));
} else {
i += 1;
}
x += 1;
}
Ok(result.into())
} }
#[rhai_fn(name = "drain")] #[rhai_fn(name = "drain")]
pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array { pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array {
@ -800,42 +857,62 @@ mod array_functions {
return Ok(Array::new()); return Ok(Array::new());
} }
let mut drained = Array::new(); let fn_name = filter.fn_name();
let mut index_val = Dynamic::UNIT;
let mut removed = Vec::with_capacity(array.len());
let mut count = 0;
let mut i = 0; for (i, item) in array.iter_mut().enumerate() {
let mut x = 0; let mut args = [item, &mut index_val];
while x < array.len() { let keep = match ctx.call_fn_raw(fn_name, true, false, &mut args[..1]) {
if !filter Ok(r) => r,
.call_dynamic(&ctx, None, [array[x].clone()]) Err(err) => match *err {
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _) EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) => if fn_sig.starts_with(fn_name) =>
{ {
filter.call_dynamic(&ctx, None, [array[x].clone(), (i as INT).into()]) *args[1] = Dynamic::from(i as INT);
ctx.call_fn_raw(fn_name, true, false, &mut args)?
} }
_ => Err(err), _ => {
}) return EvalAltResult::ErrorInFunctionCall(
.map_err(|err| { "drain".to_string(),
Box::new(EvalAltResult::ErrorInFunctionCall( ctx.source().unwrap_or("").to_string(),
"retain".to_string(), err,
ctx.source().unwrap_or("").to_string(), Position::NONE,
err, )
Position::NONE, .into()
)) }
})? },
.as_bool()
.unwrap_or(false)
{
drained.push(array.remove(x));
} else {
x += 1;
} }
.as_bool()
.unwrap_or(false);
i += 1; removed.push(!keep);
if !keep {
count += 1;
}
} }
Ok(drained) if count == 0 {
return Ok(Array::new());
}
let mut result = Vec::with_capacity(count);
let mut x = 0;
let mut i = 0;
while i < array.len() {
if removed[x] {
result.push(array.remove(i));
} else {
i += 1;
}
x += 1;
}
Ok(result.into())
} }
#[rhai_fn(name = "retain")] #[rhai_fn(name = "retain")]
pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array { pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array {
@ -884,7 +961,7 @@ mod array_functions {
for (a1, a2) in array1.iter_mut().zip(array2.iter_mut()) { for (a1, a2) in array1.iter_mut().zip(array2.iter_mut()) {
if !ctx if !ctx
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [a1, a2]) .call_fn_raw(OP_EQUALS, true, false, &mut [a1, a2])
.or_else(|err| match *err { .or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if fn_sig.starts_with(OP_EQUALS) => if fn_sig.starts_with(OP_EQUALS) =>

View File

@ -84,7 +84,7 @@ mod map_functions {
for (m1, v1) in map1.iter_mut() { for (m1, v1) in map1.iter_mut() {
if let Some(v2) = map2.get_mut(m1) { if let Some(v2) = map2.get_mut(m1) {
let equals = ctx let equals = ctx
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [v1, v2]) .call_fn_raw(OP_EQUALS, true, false, &mut [v1, v2])
.map(|v| v.as_bool().unwrap_or(false))?; .map(|v| v.as_bool().unwrap_or(false))?;
if !equals { if !equals {

View File

@ -28,7 +28,7 @@ pub fn print_with_func(
ctx: &NativeCallContext, ctx: &NativeCallContext,
value: &mut Dynamic, value: &mut Dynamic,
) -> crate::ImmutableString { ) -> crate::ImmutableString {
match ctx.call_fn_dynamic_raw(fn_name, true, &mut [value]) { match ctx.call_fn_raw(fn_name, true, false, &mut [value]) {
Ok(result) if result.is::<crate::ImmutableString>() => result Ok(result) if result.is::<crate::ImmutableString>() => result
.into_immutable_string() .into_immutable_string()
.expect("result is `ImmutableString`"), .expect("result is `ImmutableString`"),