diff --git a/CHANGELOG.md b/CHANGELOG.md index 26bb649a..ef8a0f8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ Rhai Release Notes 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 ============= diff --git a/src/deprecated.rs b/src/deprecated.rs index 407f9630..b0485a3e 100644 --- a/src/deprecated.rs +++ b/src/deprecated.rs @@ -1,6 +1,8 @@ //! 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")] use std::prelude::v1::*; @@ -141,3 +143,28 @@ impl Dynamic { 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, + is_method_call: bool, + args: &mut [&mut Dynamic], + ) -> RhaiResult { + self.call_fn_raw(fn_name.as_ref(), is_method_call, is_method_call, args) + } +} diff --git a/src/fn_native.rs b/src/fn_native.rs index 9fa9d008..15ffbbd6 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -191,48 +191,42 @@ impl<'a> NativeCallContext<'a> { /// 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 + /// 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_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, - fn_name: impl AsRef, + fn_name: &str, + is_ref_mut: bool, is_method_call: bool, args: &mut [&mut Dynamic], - ) -> RhaiResult { - fn call_fn_dynamic_inner( - context: &NativeCallContext, - is_method_call: bool, - fn_name: &str, - args: &mut [&mut Dynamic], - ) -> Result> { - let hash = if is_method_call { - 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) - } + ) -> Result> { + let hash = if is_method_call { + 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())) + }; - 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) } } diff --git a/src/fn_ptr.rs b/src/fn_ptr.rs index 3b9350cb..6fdf2030 100644 --- a/src/fn_ptr.rs +++ b/src/fn_ptr.rs @@ -130,7 +130,7 @@ impl FnPtr { } 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) } } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index f119bc0e..20fc0c7f 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -246,31 +246,37 @@ mod array_functions { mapper: FnPtr, ) -> Result> { 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 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( - mapper - .call_dynamic(&ctx, None, [item.clone()]) - .or_else(|err| match *err { + match ctx.call_fn_raw(fn_name, true, false, &mut args[..1]) { + Ok(r) => r, + Err(err) => match *err { 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), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "map".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })?, + _ => { + return EvalAltResult::ErrorInFunctionCall( + "drain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + ) + .into() + } + }, + }, ); } @@ -286,31 +292,38 @@ mod array_functions { return Ok(array.clone()); } + let fn_name = filter.fn_name(); let mut ar = Array::new(); + let mut index_val = Dynamic::UNIT; - for (i, item) in array.iter().enumerate() { - if filter - .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 keep = match ctx.call_fn_raw(fn_name, true, false, &mut args[..1]) { + Ok(r) => r, + Err(err) => match *err { 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), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "filter".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? - .as_bool() - .unwrap_or(false) - { - ar.push(item.clone()); + _ => { + return EvalAltResult::ErrorInFunctionCall( + "drain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + ) + .into() + } + }, + } + .as_bool() + .unwrap_or(false); + + if keep { + ar.push(args[0].clone()); } } @@ -328,7 +341,7 @@ mod array_functions { for item in array.iter_mut() { 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 { EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => @@ -387,7 +400,7 @@ mod array_functions { for (i, item) in array.iter_mut().enumerate().skip(start) { 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 { EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => @@ -444,28 +457,36 @@ mod array_functions { start as usize }; - for (i, item) in array.iter().enumerate().skip(start) { - if filter - .call_dynamic(&ctx, None, [item.clone()]) - .or_else(|err| match *err { + let fn_name = filter.fn_name(); + let mut index_val = Dynamic::UNIT; + + 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, _) - 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), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "index_of".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? - .as_bool() - .unwrap_or(false) - { + _ => { + return EvalAltResult::ErrorInFunctionCall( + "drain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + ) + .into() + } + }, + } + .as_bool() + .unwrap_or(false); + + if found { return Ok(i as INT); } } @@ -482,28 +503,36 @@ mod array_functions { return Ok(false); } - for (i, item) in array.iter().enumerate() { - if filter - .call_dynamic(&ctx, None, [item.clone()]) - .or_else(|err| match *err { + let fn_name = filter.fn_name(); + let mut index_val = Dynamic::UNIT; + + 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, _) - 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), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "some".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? - .as_bool() - .unwrap_or(false) - { + _ => { + return EvalAltResult::ErrorInFunctionCall( + "drain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + ) + .into() + } + }, + } + .as_bool() + .unwrap_or(false); + + if found { return Ok(true); } } @@ -520,28 +549,36 @@ mod array_functions { return Ok(true); } - for (i, item) in array.iter().enumerate() { - if !filter - .call_dynamic(&ctx, None, [item.clone()]) - .or_else(|err| match *err { + let fn_name = filter.fn_name(); + let mut index_val = Dynamic::UNIT; + + 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, _) - 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), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "all".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? - .as_bool() - .unwrap_or(false) - { + _ => { + return EvalAltResult::ErrorInFunctionCall( + "drain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + ) + .into() + } + }, + } + .as_bool() + .unwrap_or(false); + + if !found { return Ok(false); } } @@ -726,42 +763,62 @@ mod array_functions { 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; - let mut x = 0; + for (i, item) in array.iter_mut().enumerate() { + let mut args = [item, &mut index_val]; - while x < array.len() { - if filter - .call_dynamic(&ctx, None, [array[x].clone()]) - .or_else(|err| match *err { + let remove = match ctx.call_fn_raw(fn_name, true, false, &mut args[..1]) { + Ok(r) => r, + Err(err) => match *err { 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), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "drain".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? - .as_bool() - .unwrap_or(false) - { - drained.push(array.remove(x)); - } else { - x += 1; + _ => { + return EvalAltResult::ErrorInFunctionCall( + "drain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + ) + .into() + } + }, } + .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")] pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array { @@ -800,42 +857,62 @@ mod array_functions { 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; - let mut x = 0; + for (i, item) in array.iter_mut().enumerate() { + let mut args = [item, &mut index_val]; - while x < array.len() { - if !filter - .call_dynamic(&ctx, None, [array[x].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, _) - 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), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "retain".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? - .as_bool() - .unwrap_or(false) - { - drained.push(array.remove(x)); - } else { - x += 1; + _ => { + return EvalAltResult::ErrorInFunctionCall( + "drain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + ) + .into() + } + }, } + .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")] 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()) { 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 { EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index b042d9e1..4f9224f5 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -84,7 +84,7 @@ mod map_functions { for (m1, v1) in map1.iter_mut() { if let Some(v2) = map2.get_mut(m1) { 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))?; if !equals { diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 71a326a3..db534a3a 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -28,7 +28,7 @@ pub fn print_with_func( ctx: &NativeCallContext, value: &mut Dynamic, ) -> 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::() => result .into_immutable_string() .expect("result is `ImmutableString`"),