diff --git a/Cargo.toml b/Cargo.toml index 13ef119f..c450eecd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ smallvec = { version = "1.4.1", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] -default = [ "no_shared"] +default = [] plugins = [] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync diff --git a/src/any.rs b/src/any.rs index 470d86c7..1f1a6df8 100644 --- a/src/any.rs +++ b/src/any.rs @@ -578,7 +578,9 @@ impl Dynamic { } /// Convert the `Dynamic` value into specific type. - /// Casting to a `Dynamic` just returns as is. + /// + /// Casting to a `Dynamic` just returns as is, but if it contains a shared value, + /// it is cloned into a `Dynamic` with a normal value. /// /// Returns `None` if types mismatched. /// @@ -602,6 +604,19 @@ impl Dynamic { pub fn try_cast(self) -> Option { let type_id = TypeId::of::(); + match self.0 { + #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => return cell.container.borrow().deref().clone().try_cast(), + + #[cfg(not(feature = "no_shared"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => { + return cell.container.read().unwrap().deref().clone().try_cast() + } + _ => (), + } + if type_id == TypeId::of::() { return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } @@ -681,23 +696,15 @@ impl Dynamic { match self.0 { Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), - - #[cfg(not(feature = "no_shared"))] - #[cfg(not(feature = "sync"))] - Union::Shared(cell) => return cell.container.borrow().deref().clone().try_cast(), - - #[cfg(not(feature = "no_shared"))] - #[cfg(feature = "sync")] - Union::Shared(cell) => { - return cell.container.read().unwrap().deref().clone().try_cast() - } - + Union::Shared(_) => unreachable!(), _ => None, } } /// Convert the `Dynamic` value into a specific type. - /// Casting to a `Dynamic` just returns as is. + /// + /// Casting to a `Dynamic` just returns as is, but if it contains a shared value, + /// it is cloned into a `Dynamic` with a normal value. /// /// Returns `None` if types mismatched. /// diff --git a/src/engine.rs b/src/engine.rs index e3f5dad8..b3585804 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1328,12 +1328,15 @@ impl Engine { // Normal assignment ScopeEntryType::Normal if op.is_empty() => { #[cfg(not(feature = "no_shared"))] - let lhs_ptr = if lhs_ptr.is_shared() { - lhs_ptr.write_lock::().unwrap(); + if lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = rhs_val; } else { - lhs_ptr - }; - *lhs_ptr = rhs_val; + *lhs_ptr = rhs_val; + } + #[cfg(feature = "no_shared")] + { + *lhs_ptr = rhs_val; + } Ok(Default::default()) } // Op-assignment - in order of precedence: @@ -1351,14 +1354,17 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - // Overriding exact implementation + let mut lock_guard; + #[cfg(not(feature = "no_shared"))] let lhs_ptr = if lhs_ptr.is_shared() { - &mut lhs_ptr.write_lock::().unwrap() + lock_guard = Some(lhs_ptr.write_lock::().unwrap()); + lock_guard.as_deref_mut().unwrap() } else { lhs_ptr }; + // Overriding exact implementation func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` @@ -1375,13 +1381,15 @@ impl Engine { .map_err(|err| err.new_position(*op_pos))?; #[cfg(not(feature = "no_shared"))] - let lhs_ptr = if lhs_ptr.is_shared() { - lhs_ptr.write_lock::().unwrap() + if lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = value; } else { - lhs_ptr - }; - - *lhs_ptr = value; + *lhs_ptr = value; + } + #[cfg(feature = "no_shared")] + { + *lhs_ptr = value; + } } Ok(Default::default()) } diff --git a/tests/closures.rs b/tests/closures.rs index 4f8c16dd..c5282870 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -2,12 +2,6 @@ use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT}; use std::any::TypeId; -#[cfg(not(feature = "no_shared"))] -use rhai::RegisterFn; - -#[cfg(not(feature = "no_index"))] -use rhai::Array; - #[test] fn test_fn_ptr_curry_call() -> Result<(), Box> { let mut module = Module::new(); @@ -64,291 +58,3 @@ fn test_closures() -> Result<(), Box> { Ok(()) } - -#[test] -#[cfg(not(feature = "no_shared"))] -fn test_shared() -> Result<(), Box> { - let mut engine = Engine::new(); - - assert_eq!( - engine.eval::( - r#" - shared(42) - "# - )?, - 42 - ); - - assert_eq!( - engine.eval::( - r#" - shared(true) - "# - )?, - true - ); - - #[cfg(not(feature = "no_float"))] - assert_eq!( - engine.eval::( - r#" - shared(4.2) - "# - )?, - 4.2 - ); - - assert_eq!( - engine.eval::( - r#" - shared("test") - "# - )?, - "test" - ); - - assert_eq!( - engine.eval::( - r#" - shared('x') - "# - )?, - 'x' - ); - - #[cfg(not(feature = "no_index"))] - { - assert_eq!( - engine.eval::( - r#" - let s = shared("test"); - let i = shared(0); - i = 2; - s[i] = 'S'; - - s - "# - )?, - "teSt" - ); - - assert_eq!( - engine.eval::( - r#" - let x = shared([1, 2, 3]); - let y = shared([4, 5]); - x + y - "# - )?.len(), - 5 - ); - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::( - r" - let x = shared([2, 9]); - x.insert(-1, 1); - x.insert(999, 3); - - let r = x.remove(2); - - let y = shared([4, 5]); - x.append(y); - - x.len + r - " - )?, - 14 - ); - - assert_eq!( - engine.eval::( - r#" - let x = shared([1, 2, 3]); - - if x[0] + x[2] == 4 { - true - } else { - false - } - "# - )?, - true - ); - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::( - r#" - let x = shared([1, 2, 3]); - let y = shared(()); - - (|| { - for i in x { - y = i * 10; - } - }).call(); - - y - "# - )?, - 30 - ); - } - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::(r#" - let y = shared(#{a: 1, b: 2, c: 3}); - y.c = shared(5); - y.c - "#)?, - 5 - ); - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::(r#" - let y = shared(#{a: 1, b: 2, c: shared(3)}); - let c = y.c; - c = 5;// "c" holds Dynamic Shared - y.c - "#)?, - 5 - ); - - #[cfg(not(feature = "no_capture"))] - assert_eq!( - engine.eval::(r#" - let x = shared(1); - (|| x = x + 41).call(); - x - "#)?, - 42 - ); - - #[cfg(all(not(feature = "no_object"), not(feature = "no_capture")))] - assert_eq!( - engine.eval::(r#" - let x = shared(#{a: 1, b: shared(2), c: 3}); - let a = x.a; - let b = x.b; - a = 100; // does not hold reference to x.a - b = 20; // does hold reference to x.b - - let f = |a| { - x.c = x.a + x.b + a; - }; - - f.call(21); - - x.c - "#)?, - 42 - ); - - // Register a binary function named `foo` - engine.register_fn("custom_addition", |x: INT, y: INT| x + y); - - assert_eq!( - engine.eval::(r#" - custom_addition(shared(20), shared(22)) - "#)?, - 42 - ); - - #[cfg(not(feature = "no_object"))] - { - #[derive(Clone)] - struct TestStruct { - x: INT, - } - - impl TestStruct { - fn update(&mut self) { - self.x += 1000; - } - - fn merge(&mut self, other: Self) { - self.x += other.x; - } - - fn get_x(&mut self) -> INT { - self.x - } - - fn set_x(&mut self, new_x: INT) { - self.x = new_x; - } - - fn new() -> Self { - TestStruct { x: 1 } - } - } - - engine - .register_type::() - .register_get_set("x", TestStruct::get_x, TestStruct::set_x) - .register_fn("update", TestStruct::update) - .register_fn("merge", TestStruct::merge) - .register_fn("new_ts", TestStruct::new) - .register_raw_fn( - "mutate_with_cb", - &[ - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - ], - move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { - let fp = std::mem::take(args[2]).cast::(); - let mut value = args[1].clone(); - { - let mut lock = value.write_lock::().unwrap(); - *lock = *lock + 1; - } - let this_ptr = args.get_mut(0).unwrap(); - - fp.call_dynamic(engine, lib, Some(this_ptr), [value]) - }, - ); - - assert_eq!( - engine.eval::( - r" - let a = shared(new_ts()); - - a.x = 100; - a.update(); - a.merge(a.take()); // take is important to prevent a deadlock - - a.x - " - )?, - 2200 - ); - - assert_eq!( - engine.eval::( - r" - let a = shared(new_ts()); - let b = shared(100); - - a.mutate_with_cb(b, |param| { - this.x = param; - param = 50; - this.update(); - }); - - a.update(); - a.x += b; - - a.x - " - )?, - 2151 - ); - } - - Ok(()) -} diff --git a/tests/shared.rs b/tests/shared.rs new file mode 100644 index 00000000..e35de3d0 --- /dev/null +++ b/tests/shared.rs @@ -0,0 +1,268 @@ +#![cfg(not(feature = "no_shared"))] + +use rhai::{Array, Dynamic, Engine, EvalAltResult, FnPtr, Module, RegisterFn, INT}; +use std::any::TypeId; + +#[test] +fn test_shared() -> Result<(), Box> { + let mut engine = Engine::new(); + + assert_eq!(engine.eval::("shared(42)")?, 42); + + assert_eq!(engine.eval::("shared(true)")?, true); + + #[cfg(not(feature = "no_float"))] + assert_eq!(engine.eval::("shared(4.2)")?, 4.2); + + assert_eq!(engine.eval::(r#"shared("test")"#)?, "test"); + + assert_eq!(engine.eval::("shared('x')")?, 'x'); + + #[cfg(not(feature = "no_index"))] + { + assert_eq!( + engine.eval::( + r#" + let s = shared("test"); + let i = shared(0); + i = 2; + s[i] = 'S'; + + s + "# + )?, + "teSt" + ); + + assert_eq!( + engine + .eval::( + r#" + let x = shared([1, 2, 3]); + let y = shared([4, 5]); + x + y + "# + )? + .len(), + 5 + ); + + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r" + let x = shared([2, 9]); + x.insert(-1, 1); + x.insert(999, 3); + + let r = x.remove(2); + + let y = shared([4, 5]); + x.append(y); + + x.len + r + " + )?, + 14 + ); + + assert_eq!( + engine.eval::( + r#" + let x = shared([1, 2, 3]); + + if x[0] + x[2] == 4 { + true + } else { + false + } + "# + )?, + true + ); + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r#" + let x = shared([1, 2, 3]); + let y = shared(()); + + (|| { + for i in x { + y = i * 10; + } + }).call(); + + y + "# + )?, + 30 + ); + } + + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r#" + let y = shared(#{a: 1, b: 2, c: 3}); + y.c = shared(5); + y.c + "# + )?, + 5 + ); + + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r#" + let y = shared(#{a: 1, b: 2, c: shared(3)}); + let c = y.c; + c = 5;// "c" holds Dynamic Shared + y.c + "# + )?, + 5 + ); + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_capture"))] + assert_eq!( + engine.eval::( + r#" + let x = shared(1); + (|| x = x + 41).call(); + x + "# + )?, + 42 + ); + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_object"))] + #[cfg(not(feature = "no_capture"))] + assert_eq!( + engine.eval::( + r#" + let x = shared(#{a: 1, b: shared(2), c: 3}); + let a = x.a; + let b = x.b; + a = 100; // does not hold reference to x.a + b = 20; // does hold reference to x.b + + let f = |a| { + x.c = x.a + x.b + a; + }; + + f.call(21); + + x.c + "# + )?, + 42 + ); + + // Register a binary function named `foo` + engine.register_fn("custom_addition", |x: INT, y: INT| x + y); + + assert_eq!( + engine.eval::("custom_addition(shared(20), shared(22))")?, + 42 + ); + + #[cfg(not(feature = "no_object"))] + { + #[derive(Clone)] + struct TestStruct { + x: INT, + } + + impl TestStruct { + fn update(&mut self) { + self.x += 1000; + } + + fn merge(&mut self, other: Self) { + self.x += other.x; + } + + fn get_x(&mut self) -> INT { + self.x + } + + fn set_x(&mut self, new_x: INT) { + self.x = new_x; + } + + fn new() -> Self { + TestStruct { x: 1 } + } + } + + engine + .register_type::() + .register_get_set("x", TestStruct::get_x, TestStruct::set_x) + .register_fn("update", TestStruct::update) + .register_fn("merge", TestStruct::merge) + .register_fn("new_ts", TestStruct::new) + .register_raw_fn( + "mutate_with_cb", + &[ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ], + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + let fp = std::mem::take(args[2]).cast::(); + let mut value = args[1].clone(); + { + let mut lock = value.write_lock::().unwrap(); + *lock = *lock + 1; + } + let this_ptr = args.get_mut(0).unwrap(); + + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) + }, + ); + + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); + + a.x = 100; + a.update(); + a.merge(a.take()); // take is important to prevent a deadlock + + a.x + " + )?, + 2200 + ); + + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); + let b = shared(100); + + a.mutate_with_cb(b, |param| { + this.x = param; + param = 50; + this.update(); + }); + + a.update(); + a.x += b; + + a.x + " + )?, + 2151 + ); + } + + Ok(()) +}