Avoid allocation in Target.
This commit is contained in:
parent
be97047e51
commit
0cb781c1aa
28
README.md
28
README.md
@ -13,14 +13,15 @@ to add scripting to any application.
|
|||||||
|
|
||||||
Rhai's current features set:
|
Rhai's current features set:
|
||||||
|
|
||||||
* Easy-to-use language similar to JS+Rust
|
* Easy-to-use language similar to JS+Rust with dynamic typing but _no_ garbage collector
|
||||||
* Easy integration with Rust [native functions](#working-with-functions) and [types](#custom-types-and-methods),
|
* Tight integration with native Rust [functions](#working-with-functions) and [types](#custom-types-and-methods),
|
||||||
including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers)
|
including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers)
|
||||||
|
* Freely pass Rust variables/constants into a script via an external [`Scope`]
|
||||||
* Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust
|
* Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust
|
||||||
* Freely pass variables/constants into a script via an external [`Scope`]
|
* Low compile-time overhead (~0.6 sec debug/~3 sec release for `rhai_runner` sample app)
|
||||||
* Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
|
* Fairly efficient evaluation (1 million iterations in 0.75 sec on my 5 year old laptop)
|
||||||
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
|
* Relatively little `unsafe` code (yes there are some for performance reasons, and all `unsafe` code is limited to
|
||||||
* Relatively little `unsafe` code (yes there are some for performance reasons)
|
one single source file, all with names starting with `"unsafe_"`)
|
||||||
* Sand-boxed (the scripting [`Engine`] can be declared immutable which cannot mutate the containing environment
|
* Sand-boxed (the scripting [`Engine`] can be declared immutable which cannot mutate the containing environment
|
||||||
unless explicitly allowed via `RefCell` etc.)
|
unless explicitly allowed via `RefCell` etc.)
|
||||||
* Rugged (protection against [stack-overflow](#maximum-stack-depth) and [runaway scripts](#maximum-number-of-operations) etc.)
|
* Rugged (protection against [stack-overflow](#maximum-stack-depth) and [runaway scripts](#maximum-number-of-operations) etc.)
|
||||||
@ -70,20 +71,21 @@ Optional features
|
|||||||
| Feature | Description |
|
| Feature | Description |
|
||||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `unchecked` | Exclude arithmetic checking (such as over-flows and division by zero), stack depth limit and operations count limit. Beware that a bad script may panic the entire system! |
|
| `unchecked` | Exclude arithmetic checking (such as over-flows and division by zero), stack depth limit and operations count limit. Beware that a bad script may panic the entire system! |
|
||||||
| `no_function` | Disable script-defined functions if not needed. |
|
| `no_function` | Disable script-defined functions. |
|
||||||
| `no_index` | Disable [arrays] and indexing features if not needed. |
|
| `no_index` | Disable [arrays] and indexing features. |
|
||||||
| `no_object` | Disable support for custom types and objects. |
|
| `no_object` | Disable support for custom types and object maps. |
|
||||||
| `no_float` | Disable floating-point numbers and math if not needed. |
|
| `no_float` | Disable floating-point numbers and math. |
|
||||||
| `no_optimize` | Disable the script optimizer. |
|
| `no_optimize` | Disable the script optimizer. |
|
||||||
| `no_module` | Disable modules. |
|
| `no_module` | Disable modules. |
|
||||||
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
||||||
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||||
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. |
|
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and `AST`, are all `Send + Sync`. |
|
||||||
|
|
||||||
By default, Rhai includes all the standard functionalities in a small, tight package.
|
By default, Rhai includes all the standard functionalities in a small, tight package.
|
||||||
Most features are here to opt-**out** of certain functionalities that are not needed.
|
Most features are here to opt-**out** of certain functionalities that are not needed.
|
||||||
Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language.
|
Excluding unneeded functionalities can result in smaller, faster builds
|
||||||
|
as well as more control over what a script can (or cannot) do.
|
||||||
|
|
||||||
[`unchecked`]: #optional-features
|
[`unchecked`]: #optional-features
|
||||||
[`no_index`]: #optional-features
|
[`no_index`]: #optional-features
|
||||||
@ -967,7 +969,7 @@ Indexers
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
Custom types can also expose an _indexer_ by registering an indexer function.
|
Custom types can also expose an _indexer_ by registering an indexer function.
|
||||||
A custom with an indexer function defined can use the bracket '`[]`' notation to get a property value
|
A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value
|
||||||
(but not update it - indexers are read-only).
|
(but not update it - indexers are read-only).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -72,19 +72,36 @@ enum Target<'a> {
|
|||||||
/// The target is a mutable reference to a `Dynamic` value somewhere.
|
/// The target is a mutable reference to a `Dynamic` value somewhere.
|
||||||
Ref(&'a mut Dynamic),
|
Ref(&'a mut Dynamic),
|
||||||
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
|
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
|
||||||
Value(Box<Dynamic>),
|
Value(Dynamic),
|
||||||
/// The target is a character inside a String.
|
/// The target is a character inside a String.
|
||||||
/// This is necessary because directly pointing to a char inside a String is impossible.
|
/// This is necessary because directly pointing to a char inside a String is impossible.
|
||||||
StringChar(Box<(&'a mut Dynamic, usize, Dynamic)>),
|
StringChar(&'a mut Dynamic, usize, Dynamic),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Target<'_> {
|
impl Target<'_> {
|
||||||
/// Get the value of the `Target` as a `Dynamic`.
|
/// Is the `Target` a reference pointing to other data?
|
||||||
|
pub fn is_ref(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Target::Ref(_) => true,
|
||||||
|
Target::Value(_) | Target::StringChar(_, _, _) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary.
|
||||||
pub fn clone_into_dynamic(self) -> Dynamic {
|
pub fn clone_into_dynamic(self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Target::Ref(r) => r.clone(),
|
Target::Ref(r) => r.clone(), // Referenced value is cloned
|
||||||
Target::Value(v) => *v,
|
Target::Value(v) => v, // Owned value is simply taken
|
||||||
Target::StringChar(s) => s.2,
|
Target::StringChar(_, _, ch) => ch, // Character is taken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference from the `Target`.
|
||||||
|
pub fn as_mut(&mut self) -> &mut Dynamic {
|
||||||
|
match self {
|
||||||
|
Target::Ref(r) => *r,
|
||||||
|
Target::Value(ref mut r) => r,
|
||||||
|
Target::StringChar(_, _, ref mut r) => r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,25 +112,23 @@ impl Target<'_> {
|
|||||||
Target::Value(_) => {
|
Target::Value(_) => {
|
||||||
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(pos)))
|
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(pos)))
|
||||||
}
|
}
|
||||||
Target::StringChar(x) => match x.0 {
|
Target::StringChar(Dynamic(Union::Str(s)), index, _) => {
|
||||||
Dynamic(Union::Str(s)) => {
|
|
||||||
// Replace the character at the specified index position
|
// Replace the character at the specified index position
|
||||||
let new_ch = new_val
|
let new_ch = new_val
|
||||||
.as_char()
|
.as_char()
|
||||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
||||||
|
|
||||||
let mut chars: StaticVec<char> = s.chars().collect();
|
let mut chars: StaticVec<char> = s.chars().collect();
|
||||||
let ch = *chars.get_ref(x.1);
|
let ch = *chars.get_ref(*index);
|
||||||
|
|
||||||
// See if changed - if so, update the String
|
// See if changed - if so, update the String
|
||||||
if ch != new_ch {
|
if ch != new_ch {
|
||||||
*chars.get_mut(x.1) = new_ch;
|
*chars.get_mut(*index) = new_ch;
|
||||||
s.clear();
|
s.clear();
|
||||||
chars.iter().for_each(|&ch| s.push(ch));
|
chars.iter().for_each(|&ch| s.push(ch));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -127,7 +142,7 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
|
|||||||
}
|
}
|
||||||
impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
||||||
fn from(value: T) -> Self {
|
fn from(value: T) -> Self {
|
||||||
Self::Value(Box::new(value.into()))
|
Self::Value(value.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -913,7 +928,7 @@ impl Engine {
|
|||||||
fn eval_dot_index_chain_helper(
|
fn eval_dot_index_chain_helper(
|
||||||
&self,
|
&self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
mut target: Target,
|
target: &mut Target,
|
||||||
rhs: &Expr,
|
rhs: &Expr,
|
||||||
idx_values: &mut StaticVec<Dynamic>,
|
idx_values: &mut StaticVec<Dynamic>,
|
||||||
is_index: bool,
|
is_index: bool,
|
||||||
@ -921,12 +936,10 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
mut new_val: Option<Dynamic>,
|
mut new_val: Option<Dynamic>,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
|
let is_ref = target.is_ref();
|
||||||
|
|
||||||
// Get a reference to the mutation target Dynamic
|
// Get a reference to the mutation target Dynamic
|
||||||
let (obj, is_ref) = match target {
|
let obj = target.as_mut();
|
||||||
Target::Ref(r) => (r, true),
|
|
||||||
Target::Value(ref mut r) => (r.as_mut(), false),
|
|
||||||
Target::StringChar(ref mut x) => (&mut x.2, false),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pop the last index value
|
// Pop the last index value
|
||||||
let mut idx_val = idx_values.pop();
|
let mut idx_val = idx_values.pop();
|
||||||
@ -937,20 +950,20 @@ impl Engine {
|
|||||||
Expr::Dot(x) | Expr::Index(x) => {
|
Expr::Dot(x) | Expr::Index(x) => {
|
||||||
let is_idx = matches!(rhs, Expr::Index(_));
|
let is_idx = matches!(rhs, Expr::Index(_));
|
||||||
let pos = x.0.position();
|
let pos = x.0.position();
|
||||||
let val =
|
let this_ptr = &mut self
|
||||||
self.get_indexed_mut(state, obj, is_ref, idx_val, pos, op_pos, false)?;
|
.get_indexed_mut(state, obj, is_ref, idx_val, pos, op_pos, false)?;
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, val, &x.1, idx_values, is_idx, x.2, level, new_val,
|
state, this_ptr, &x.1, idx_values, is_idx, x.2, level, new_val,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// xxx[rhs] = new_val
|
// xxx[rhs] = new_val
|
||||||
_ if new_val.is_some() => {
|
_ if new_val.is_some() => {
|
||||||
let pos = rhs.position();
|
let pos = rhs.position();
|
||||||
let mut val =
|
let this_ptr = &mut self
|
||||||
self.get_indexed_mut(state, obj, is_ref, idx_val, pos, op_pos, true)?;
|
.get_indexed_mut(state, obj, is_ref, idx_val, pos, op_pos, true)?;
|
||||||
|
|
||||||
val.set_value(new_val.unwrap(), rhs.position())?;
|
this_ptr.set_value(new_val.unwrap(), rhs.position())?;
|
||||||
Ok((Default::default(), true))
|
Ok((Default::default(), true))
|
||||||
}
|
}
|
||||||
// xxx[rhs]
|
// xxx[rhs]
|
||||||
@ -1019,7 +1032,7 @@ impl Engine {
|
|||||||
Expr::Index(x) | Expr::Dot(x) if obj.is::<Map>() => {
|
Expr::Index(x) | Expr::Dot(x) if obj.is::<Map>() => {
|
||||||
let is_idx = matches!(rhs, Expr::Index(_));
|
let is_idx = matches!(rhs, Expr::Index(_));
|
||||||
|
|
||||||
let val = if let Expr::Property(p) = &x.0 {
|
let mut val = if let Expr::Property(p) = &x.0 {
|
||||||
let ((prop, _, _), _) = p.as_ref();
|
let ((prop, _, _), _) = p.as_ref();
|
||||||
let index = prop.clone().into();
|
let index = prop.clone().into();
|
||||||
self.get_indexed_mut(state, obj, is_ref, index, x.2, op_pos, false)?
|
self.get_indexed_mut(state, obj, is_ref, index, x.2, op_pos, false)?
|
||||||
@ -1032,7 +1045,7 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, val, &x.1, idx_values, is_idx, x.2, level, new_val,
|
state, &mut val, &x.1, idx_values, is_idx, x.2, level, new_val,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// xxx.idx_lhs[idx_expr] | xxx.dot_lhs.rhs
|
// xxx.idx_lhs[idx_expr] | xxx.dot_lhs.rhs
|
||||||
@ -1051,16 +1064,10 @@ impl Engine {
|
|||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
let val = &mut val;
|
let val = &mut val;
|
||||||
|
let target = &mut val.into();
|
||||||
|
|
||||||
let (result, may_be_changed) = self.eval_dot_index_chain_helper(
|
let (result, may_be_changed) = self.eval_dot_index_chain_helper(
|
||||||
state,
|
state, target, &x.1, idx_values, is_idx, x.2, level, new_val,
|
||||||
val.into(),
|
|
||||||
&x.1,
|
|
||||||
idx_values,
|
|
||||||
is_idx,
|
|
||||||
x.2,
|
|
||||||
level,
|
|
||||||
new_val,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Feed the value back via a setter just in case it has been updated
|
// Feed the value back via a setter just in case it has been updated
|
||||||
@ -1125,7 +1132,7 @@ impl Engine {
|
|||||||
ScopeEntryType::Constant | ScopeEntryType::Normal => (),
|
ScopeEntryType::Constant | ScopeEntryType::Normal => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let this_ptr = target.into();
|
let this_ptr = &mut target.into();
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
|
state, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
|
||||||
)
|
)
|
||||||
@ -1140,7 +1147,7 @@ impl Engine {
|
|||||||
// {expr}.??? or {expr}[???]
|
// {expr}.??? or {expr}[???]
|
||||||
expr => {
|
expr => {
|
||||||
let val = self.eval_expr(scope, state, expr, level)?;
|
let val = self.eval_expr(scope, state, expr, level)?;
|
||||||
let this_ptr = val.into();
|
let this_ptr = &mut val.into();
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
|
state, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
|
||||||
)
|
)
|
||||||
@ -1258,7 +1265,7 @@ impl Engine {
|
|||||||
let ch = s.chars().nth(offset).ok_or_else(|| {
|
let ch = s.chars().nth(offset).ok_or_else(|| {
|
||||||
Box::new(EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos))
|
Box::new(EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos))
|
||||||
})?;
|
})?;
|
||||||
Ok(Target::StringChar(Box::new((val, offset, ch.into()))))
|
Ok(Target::StringChar(val, offset, ch.into()))
|
||||||
} else {
|
} else {
|
||||||
Err(Box::new(EvalAltResult::ErrorStringBounds(
|
Err(Box::new(EvalAltResult::ErrorStringBounds(
|
||||||
chars_len, index, idx_pos,
|
chars_len, index, idx_pos,
|
||||||
|
@ -56,6 +56,8 @@ pub fn unsafe_cast_var_name<'s>(name: &str, state: &State) -> Cow<'s, str> {
|
|||||||
// this is safe because all local variables are cleared at the end of the block
|
// this is safe because all local variables are cleared at the end of the block
|
||||||
unsafe { mem::transmute::<_, &'s str>(name) }.into()
|
unsafe { mem::transmute::<_, &'s str>(name) }.into()
|
||||||
} else {
|
} else {
|
||||||
|
// The variable is introduced at global (top) level and may persist after the script run.
|
||||||
|
// Therefore, clone the variable name.
|
||||||
name.to_string().into()
|
name.to_string().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,20 +43,20 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
engine.register_indexer(|value: &mut TestStruct, index: INT| value.array[index as usize]);
|
engine.register_indexer(|value: &mut TestStruct, index: String| value.array[index.len()]);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
|
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);
|
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0);
|
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a[3]")?, 4);
|
assert_eq!(engine.eval::<INT>(r#"let a = new_ts(); a["abc"]"#)?, 4);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_big_get_set() -> Result<(), Box<EvalAltResult>> {
|
fn test_get_set_chain() -> Result<(), Box<EvalAltResult>> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestChild {
|
struct TestChild {
|
||||||
x: INT,
|
x: INT,
|
||||||
|
Loading…
Reference in New Issue
Block a user