Add sync feature to make Dynamic, Scope and AST Send + Sync.

This commit is contained in:
Stephen Chung 2020-04-02 19:40:02 +08:00
parent 2c86abc58c
commit 0873bdc152
4 changed files with 56 additions and 14 deletions

View File

@ -32,6 +32,7 @@ no_optimize = [] # no script optimizer
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
sync = [] # restrict to only types that implement Send + Sync
# compiling for no-std # compiling for no-std
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ] no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ]

View File

@ -68,6 +68,7 @@ Optional features
| `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, [`Scope`] and `AST` are both `Send + Sync`. |
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. 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.
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 less bugs due to a more restricted language.
@ -82,6 +83,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well
[`only_i32`]: #optional-features [`only_i32`]: #optional-features
[`only_i64`]: #optional-features [`only_i64`]: #optional-features
[`no_std`]: #optional-features [`no_std`]: #optional-features
[`sync`]: #optional-features
Related Related
------- -------
@ -312,12 +314,12 @@ if type_of(x) == "string" {
} }
``` ```
Dynamic values `Dynamic` values
-------------- ----------------
[`Dynamic`]: #dynamic-values [`Dynamic`]: #dynamic-values
A `Dynamic` value can be _any_ type. A `Dynamic` value can be _any_ type. However, if the [`sync`] feature is used, then all types must be `Send + Sync`.
Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific
actions based on the actual value's type. actions based on the actual value's type.
@ -704,6 +706,9 @@ By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting on
This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next, This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next,
such a state must be manually created and passed in. such a state must be manually created and passed in.
All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however, then only types
that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. This is extremely useful in multi-threaded applications.
In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is threaded through multiple invocations: In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is threaded through multiple invocations:
```rust ```rust

View File

@ -1,7 +1,7 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, Any as StdAny, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
fmt, fmt,
}; };
@ -13,7 +13,8 @@ pub type Variant = dyn Any;
pub type Dynamic = Box<Variant>; pub type Dynamic = Box<Variant>;
/// A trait covering any type. /// A trait covering any type.
pub trait Any: StdAny { #[cfg(feature = "sync")]
pub trait Any: crate::stdlib::any::Any + Send + Sync {
/// Get the `TypeId` of this type. /// Get the `TypeId` of this type.
fn type_id(&self) -> TypeId; fn type_id(&self) -> TypeId;
@ -28,7 +29,43 @@ pub trait Any: StdAny {
fn _closed(&self) -> _Private; fn _closed(&self) -> _Private;
} }
impl<T: Clone + StdAny + ?Sized> Any for T { #[cfg(feature = "sync")]
impl<T: crate::stdlib::any::Any + Clone + Send + Sync + ?Sized> Any for T {
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
fn type_name(&self) -> &'static str {
type_name::<T>()
}
fn into_dynamic(&self) -> Dynamic {
Box::new(self.clone())
}
fn _closed(&self) -> _Private {
_Private
}
}
#[cfg(not(feature = "sync"))]
pub trait Any: crate::stdlib::any::Any {
/// Get the `TypeId` of this type.
fn type_id(&self) -> TypeId;
/// Get the name of this type.
fn type_name(&self) -> &'static str;
/// Convert into `Dynamic`.
fn into_dynamic(&self) -> Dynamic;
/// This trait may only be implemented by `rhai`.
#[doc(hidden)]
fn _closed(&self) -> _Private;
}
#[cfg(not(feature = "sync"))]
impl<T: crate::stdlib::any::Any + Clone + ?Sized> Any for T {
fn type_id(&self) -> TypeId { fn type_id(&self) -> TypeId {
TypeId::of::<T>() TypeId::of::<T>()
} }

View File

@ -2,8 +2,7 @@
///! This test simulates an external command object that is driven by a script. ///! This test simulates an external command object that is driven by a script.
use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT}; use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
use std::cell::RefCell; use std::sync::{Arc, Mutex};
use std::rc::Rc;
/// External command. /// External command.
struct Command { struct Command {
@ -24,19 +23,19 @@ impl Command {
/// Wrapper object to wrap a command object. /// Wrapper object to wrap a command object.
#[derive(Clone)] #[derive(Clone)]
struct CommandWrapper { struct CommandWrapper {
command: Rc<RefCell<Command>>, command: Arc<Mutex<Command>>,
} }
impl CommandWrapper { impl CommandWrapper {
/// Delegate command action. /// Delegate command action.
pub fn do_action(&mut self, x: i64) { pub fn do_action(&mut self, x: i64) {
let mut command = self.command.borrow_mut(); let mut command = self.command.lock().unwrap();
let val = command.get(); let val = command.get();
command.action(val + x); command.action(val + x);
} }
/// Delegate get value action. /// Delegate get value action.
pub fn get_value(&mut self) -> i64 { pub fn get_value(&mut self) -> i64 {
let command = self.command.borrow(); let command = self.command.lock().unwrap();
command.get() command.get()
} }
} }
@ -47,8 +46,8 @@ fn test_side_effects() -> Result<(), EvalAltResult> {
let mut scope = Scope::new(); let mut scope = Scope::new();
// Create the command object with initial state, handled by an `Rc`. // Create the command object with initial state, handled by an `Rc`.
let command = Rc::new(RefCell::new(Command { state: 12 })); let command = Arc::new(Mutex::new(Command { state: 12 }));
assert_eq!(command.borrow().get(), 12); assert_eq!(command.lock().unwrap().get(), 12);
// Create the wrapper. // Create the wrapper.
let wrapper = CommandWrapper { let wrapper = CommandWrapper {
@ -76,7 +75,7 @@ fn test_side_effects() -> Result<(), EvalAltResult> {
); );
// Make sure the actions are properly performed // Make sure the actions are properly performed
assert_eq!(command.borrow().get(), 42); assert_eq!(command.lock().unwrap().get(), 42);
Ok(()) Ok(())
} }