diff --git a/src/api/build_type.rs b/src/api/build_type.rs new file mode 100644 index 00000000..98cc8645 --- /dev/null +++ b/src/api/build_type.rs @@ -0,0 +1,329 @@ +use core::marker::PhantomData; + +use crate::{ + func::SendSync, types::dynamic::Variant, Engine, Identifier, RegisterNativeFunction, + RhaiResultOf, +}; + +/// Trait to build a custom type for use with the [`Engine`]. +/// i.e. register the type and its getters, setters, methods, etc... +/// +/// # Example +/// +/// ``` +/// use rhai::{CustomType, TypeBuilder, Engine}; +/// +/// #[derive(Debug, Clone, Eq, PartialEq)] +/// struct TestStruct { +/// field: i64 +/// } +/// +/// impl TestStruct { +/// fn new() -> Self { +/// Self { field: 1 } +/// } +/// fn update(&mut self, offset: i64) { +/// self.field += offset; +/// } +/// fn get_value(&mut self) -> i64 { +/// self.field +/// } +/// fn set_value(&mut self, value: i64) { +/// self.field = value; +/// } +/// } +/// +/// impl CustomType for TestStruct { +/// fn build(mut builder: TypeBuilder) { +/// builder +/// .with_name("TestStruct") +/// .with_fn("new_ts", Self::new) +/// .with_fn("update", Self::update) +/// .with_get_set("value", Self::get_value, Self::set_value); +/// } +/// } +/// +/// # fn main() -> Result<(), Box> { +/// +/// let mut engine = Engine::new(); +/// +/// // Register API for the custom type. +/// engine.build_type::(); +/// +/// +/// # #[cfg(not(feature = "no_object"))] +/// assert_eq!( +/// engine.eval::("let x = new_ts(); x.update(41); x")?, +/// TestStruct { field: 42 } +/// ); +/// +/// # #[cfg(not(feature = "no_object"))] +/// assert_eq!( +/// engine.eval::("let x = new_ts(); x.value = 5 + x.value; x")?, +/// TestStruct { field: 6 } +/// ); +/// # Ok(()) +/// # } +/// ``` +pub trait CustomType: Variant + Clone { + /// Builds the custom type for use with the [`Engine`]. + /// i.e. register the type, getters, setters, methods, etc... + fn build(builder: TypeBuilder); +} + +impl Engine { + /// Build a custom type for use with the [`Engine`]. + /// i.e. register the type and its getters, setters, methods, etc... + /// + /// See [`CustomType`]. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn build_type(&mut self) -> &mut Self + where + T: CustomType, + { + T::build(TypeBuilder::new(self)); + self + } +} + +/// Builder to build a custom type i.e. register this type and its getters, setters, methods, etc... +/// +/// The type is automatically registered when this builder is dropped. +/// +/// ## Pretty name +/// By default the type is registered with [`Engine::register_type`] i.e. without a pretty name. +/// +/// To define a pretty name call `.with_name`, in this case [`Engine::register_type_with_name`] will be used. +pub struct TypeBuilder<'a, T> +where + T: Variant + Clone, +{ + engine: &'a mut Engine, + name: Option<&'static str>, + _marker: PhantomData, +} + +impl<'a, T> TypeBuilder<'a, T> +where + T: Variant + Clone, +{ + #[inline] + fn new(engine: &'a mut Engine) -> Self { + Self { + engine, + name: None, + _marker: PhantomData::default(), + } + } +} + +impl<'a, T> TypeBuilder<'a, T> +where + T: Variant + Clone, +{ + /// Sets a pretty-print name for the `type_of` function. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_name(&mut self, name: &'static str) -> &mut Self { + self.name = Some(name); + self + } + + /// Register a custom function. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_fn(&mut self, name: N, method: F) -> &mut Self + where + N: AsRef + Into, + F: RegisterNativeFunction, + { + self.engine.register_fn(name, method); + self + } + + /// Register a custom fallible function. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_result_fn(&mut self, name: N, method: F) -> &mut Self + where + N: AsRef + Into, + F: RegisterNativeFunction>, + { + self.engine.register_result_fn(name, method); + self + } +} + +#[cfg(not(feature = "no_object"))] +impl<'a, T> TypeBuilder<'a, T> +where + T: Variant + Clone, +{ + /// Register a getter function. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under `no_object`. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_get( + &mut self, + name: impl AsRef, + get_fn: impl Fn(&mut T) -> V + SendSync + 'static, + ) -> &mut Self { + self.engine.register_get(name, get_fn); + self + } + + /// Register a fallible getter function. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under `no_object`. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_get_result( + &mut self, + name: impl AsRef, + get_fn: impl Fn(&mut T) -> RhaiResultOf + SendSync + 'static, + ) -> &mut Self { + self.engine.register_get_result(name, get_fn); + self + } + + /// Register a setter function. + /// + /// Not available under `no_object`. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_set( + &mut self, + name: impl AsRef, + set_fn: impl Fn(&mut T, V) + SendSync + 'static, + ) -> &mut Self { + self.engine.register_set(name, set_fn); + self + } + + /// Register a fallible setter function. + /// + /// Not available under `no_object`. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_set_result( + &mut self, + name: impl AsRef, + set_fn: impl Fn(&mut T, V) -> RhaiResultOf<()> + SendSync + 'static, + ) -> &mut Self { + self.engine.register_set_result(name, set_fn); + self + } + + /// Short-hand for registering both getter and setter functions. + /// + /// All function signatures must start with `&mut self` and not `&self`. + /// + /// Not available under `no_object`. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_get_set( + &mut self, + name: impl AsRef, + get_fn: impl Fn(&mut T) -> V + SendSync + 'static, + set_fn: impl Fn(&mut T, V) + SendSync + 'static, + ) -> &mut Self { + self.engine.register_get_set(name, get_fn, set_fn); + self + } +} + +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +impl<'a, T> TypeBuilder<'a, T> +where + T: Variant + Clone, +{ + /// Register an index getter. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under both `no_index` and `no_object`. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_indexer_get( + &mut self, + get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, + ) -> &mut Self { + self.engine.register_indexer_get(get_fn); + self + } + + /// Register an fallible index getter. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under both `no_index` and `no_object`. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_indexer_get_result( + &mut self, + get_fn: impl Fn(&mut T, X) -> RhaiResultOf + SendSync + 'static, + ) -> &mut Self { + self.engine.register_indexer_get_result(get_fn); + self + } + + /// Register an index setter. + /// + /// Not available under both `no_index` and `no_object`. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_indexer_set( + &mut self, + set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, + ) -> &mut Self { + self.engine.register_indexer_set(set_fn); + self + } + + /// Register an fallible index setter. + /// + /// Not available under both `no_index` and `no_object`. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_indexer_set_result( + &mut self, + set_fn: impl Fn(&mut T, X, V) -> RhaiResultOf<()> + SendSync + 'static, + ) -> &mut Self { + self.engine.register_indexer_set_result(set_fn); + self + } + + /// Short-hand for registering both index getter and setter functions. + /// + /// Not available under both `no_index` and `no_object`. + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn with_indexer_get_set( + &mut self, + get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, + set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, + ) -> &mut Self { + self.engine.register_indexer_get_set(get_fn, set_fn); + self + } +} + +impl<'a, T> Drop for TypeBuilder<'a, T> +where + T: Variant + Clone, +{ + #[inline] + fn drop(&mut self) { + if let Some(name) = self.name { + self.engine.register_type_with_name::(name); + } else { + self.engine.register_type::(); + } + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index f24777c6..635082dd 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -28,6 +28,8 @@ pub mod custom_syntax; pub mod deprecated; +pub mod build_type; + #[cfg(feature = "metadata")] pub mod definitions; diff --git a/src/lib.rs b/src/lib.rs index b39606d3..9808ddce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,7 +169,12 @@ type InclusiveRange = std::ops::RangeInclusive; #[cfg(not(feature = "no_std"))] #[cfg(not(target_family = "wasm"))] pub use api::files::{eval_file, run_file}; -pub use api::{eval::eval, events::VarDefInfo, run::run}; +pub use api::{ + build_type::{CustomType, TypeBuilder}, + eval::eval, + events::VarDefInfo, + run::run, +}; pub use ast::{FnAccess, AST}; pub use engine::{Engine, OP_CONTAINS, OP_EQUALS}; pub use eval::EvalContext; diff --git a/tests/build_type.rs b/tests/build_type.rs new file mode 100644 index 00000000..76d1bcac --- /dev/null +++ b/tests/build_type.rs @@ -0,0 +1,118 @@ +use rhai::{CustomType, Engine, EvalAltResult, Position, TypeBuilder}; + +#[test] +fn build_type() -> Result<(), Box> { + #[derive(Debug, Clone, PartialEq)] + struct Vec3 { + x: i64, + y: i64, + z: i64, + } + + impl Vec3 { + fn new(x: i64, y: i64, z: i64) -> Self { + Self { x, y, z } + } + fn get_x(&mut self) -> i64 { + self.x + } + fn set_x(&mut self, x: i64) { + self.x = x + } + fn get_y(&mut self) -> i64 { + self.y + } + fn set_y(&mut self, y: i64) { + self.y = y + } + fn get_z(&mut self) -> i64 { + self.z + } + fn set_z(&mut self, z: i64) { + self.z = z + } + fn get_component(&mut self, idx: i64) -> Result> { + match idx { + 0 => Ok(self.x), + 1 => Ok(self.y), + 2 => Ok(self.z), + _ => Err(Box::new(EvalAltResult::ErrorIndexNotFound( + idx.into(), + Position::NONE, + ))), + } + } + } + + impl CustomType for Vec3 { + fn build(mut builder: TypeBuilder) { + builder + .with_name("Vec3") + .with_fn("vec3", Self::new) + .with_get_set("x", Self::get_x, Self::set_x) + .with_get_set("y", Self::get_y, Self::set_y) + .with_get_set("z", Self::get_z, Self::set_z) + .with_indexer_get_result(Self::get_component); + } + } + + let mut engine = Engine::new(); + engine.build_type::(); + + assert_eq!( + engine.eval::( + r#" + let v = vec3(1, 2, 3); + v +"#, + )?, + Vec3::new(1, 2, 3), + ); + assert_eq!( + engine.eval::( + r#" + let v = vec3(1, 2, 3); + v.x +"#, + )?, + 1, + ); + assert_eq!( + engine.eval::( + r#" + let v = vec3(1, 2, 3); + v.y +"#, + )?, + 2, + ); + assert_eq!( + engine.eval::( + r#" + let v = vec3(1, 2, 3); + v.z +"#, + )?, + 3, + ); + assert!(engine.eval::( + r#" + let v = vec3(1, 2, 3); + v.x == v[0] && v.y == v[1] && v.z == v[2] +"#, + )?); + assert_eq!( + engine.eval::( + r#" + let v = vec3(1, 2, 3); + v.x = 5; + v.y = 6; + v.z = 7; + v +"#, + )?, + Vec3::new(5, 6, 7), + ); + + Ok(()) +}