This commit is contained in:
Stephen Chung 2020-07-31 16:39:38 +08:00
parent 871fcb38be
commit e70e0ff4e2
5 changed files with 310 additions and 321 deletions

View File

@ -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

View File

@ -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<T: Variant>(self) -> Option<T> {
let type_id = TypeId::of::<T>();
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::<Dynamic>() {
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.
///

View File

@ -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::<Dynamic>().unwrap();
if lhs_ptr.is_shared() {
*lhs_ptr.write_lock::<Dynamic>().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::<Dynamic>().unwrap()
lock_guard = Some(lhs_ptr.write_lock::<Dynamic>().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::<Dynamic>().unwrap()
if lhs_ptr.is_shared() {
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
} else {
lhs_ptr
};
*lhs_ptr = value;
*lhs_ptr = value;
}
#[cfg(feature = "no_shared")]
{
*lhs_ptr = value;
}
}
Ok(Default::default())
}

View File

@ -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<EvalAltResult>> {
let mut module = Module::new();
@ -64,291 +58,3 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
#[cfg(not(feature = "no_shared"))]
fn test_shared() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
shared(42)
"#
)?,
42
);
assert_eq!(
engine.eval::<bool>(
r#"
shared(true)
"#
)?,
true
);
#[cfg(not(feature = "no_float"))]
assert_eq!(
engine.eval::<f64>(
r#"
shared(4.2)
"#
)?,
4.2
);
assert_eq!(
engine.eval::<String>(
r#"
shared("test")
"#
)?,
"test"
);
assert_eq!(
engine.eval::<char>(
r#"
shared('x')
"#
)?,
'x'
);
#[cfg(not(feature = "no_index"))]
{
assert_eq!(
engine.eval::<String>(
r#"
let s = shared("test");
let i = shared(0);
i = 2;
s[i] = 'S';
s
"#
)?,
"teSt"
);
assert_eq!(
engine.eval::<Array>(
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::<INT>(
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::<bool>(
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::<INT>(
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::<INT>(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::<INT>(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::<INT>(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::<INT>(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::<INT>(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::<TestStruct>()
.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::<TestStruct>(),
TypeId::of::<INT>(),
TypeId::of::<FnPtr>(),
],
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fp = std::mem::take(args[2]).cast::<FnPtr>();
let mut value = args[1].clone();
{
let mut lock = value.write_lock::<INT>().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::<INT>(
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::<INT>(
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(())
}

268
tests/shared.rs Normal file
View File

@ -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<EvalAltResult>> {
let mut engine = Engine::new();
assert_eq!(engine.eval::<INT>("shared(42)")?, 42);
assert_eq!(engine.eval::<bool>("shared(true)")?, true);
#[cfg(not(feature = "no_float"))]
assert_eq!(engine.eval::<f64>("shared(4.2)")?, 4.2);
assert_eq!(engine.eval::<String>(r#"shared("test")"#)?, "test");
assert_eq!(engine.eval::<char>("shared('x')")?, 'x');
#[cfg(not(feature = "no_index"))]
{
assert_eq!(
engine.eval::<String>(
r#"
let s = shared("test");
let i = shared(0);
i = 2;
s[i] = 'S';
s
"#
)?,
"teSt"
);
assert_eq!(
engine
.eval::<Array>(
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::<INT>(
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::<bool>(
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::<INT>(
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::<INT>(
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::<INT>(
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::<INT>(
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::<INT>(
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::<INT>("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::<TestStruct>()
.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::<TestStruct>(),
TypeId::of::<INT>(),
TypeId::of::<FnPtr>(),
],
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fp = std::mem::take(args[2]).cast::<FnPtr>();
let mut value = args[1].clone();
{
let mut lock = value.write_lock::<INT>().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::<INT>(
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::<INT>(
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(())
}