diff --git a/Cargo.toml b/Cargo.toml index 86cedc4a..f852d289 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 only_i32 = [] # set INT=i32 (useful for 32-bit systems) 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 no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ] diff --git a/README.md b/README.md index 4d4b62aa..b8b58cf1 100644 --- a/README.md +++ b/README.md @@ -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_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. | +| `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. 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_i64`]: #optional-features [`no_std`]: #optional-features +[`sync`]: #optional-features Related ------- @@ -312,12 +314,12 @@ if type_of(x) == "string" { } ``` -Dynamic values --------------- +`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 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, 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: ```rust diff --git a/src/any.rs b/src/any.rs index befadcce..3599f972 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,7 +1,7 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. use crate::stdlib::{ - any::{type_name, Any as StdAny, TypeId}, + any::{type_name, TypeId}, boxed::Box, fmt, }; @@ -13,7 +13,8 @@ pub type Variant = dyn Any; pub type Dynamic = Box; /// 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. fn type_id(&self) -> TypeId; @@ -28,7 +29,43 @@ pub trait Any: StdAny { fn _closed(&self) -> _Private; } -impl Any for T { +#[cfg(feature = "sync")] +impl Any for T { + fn type_id(&self) -> TypeId { + TypeId::of::() + } + + fn type_name(&self) -> &'static str { + type_name::() + } + + 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 Any for T { fn type_id(&self) -> TypeId { TypeId::of::() } diff --git a/tests/side_effects.rs b/tests/side_effects.rs index 54b25432..d97b6f33 100644 --- a/tests/side_effects.rs +++ b/tests/side_effects.rs @@ -2,8 +2,7 @@ ///! This test simulates an external command object that is driven by a script. use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT}; -use std::cell::RefCell; -use std::rc::Rc; +use std::sync::{Arc, Mutex}; /// External command. struct Command { @@ -24,19 +23,19 @@ impl Command { /// Wrapper object to wrap a command object. #[derive(Clone)] struct CommandWrapper { - command: Rc>, + command: Arc>, } impl CommandWrapper { /// Delegate command action. 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(); command.action(val + x); } /// Delegate get value action. pub fn get_value(&mut self) -> i64 { - let command = self.command.borrow(); + let command = self.command.lock().unwrap(); command.get() } } @@ -47,8 +46,8 @@ fn test_side_effects() -> Result<(), EvalAltResult> { let mut scope = Scope::new(); // Create the command object with initial state, handled by an `Rc`. - let command = Rc::new(RefCell::new(Command { state: 12 })); - assert_eq!(command.borrow().get(), 12); + let command = Arc::new(Mutex::new(Command { state: 12 })); + assert_eq!(command.lock().unwrap().get(), 12); // Create the wrapper. let wrapper = CommandWrapper { @@ -76,7 +75,7 @@ fn test_side_effects() -> Result<(), EvalAltResult> { ); // Make sure the actions are properly performed - assert_eq!(command.borrow().get(), 42); + assert_eq!(command.lock().unwrap().get(), 42); Ok(()) }