diff --git a/RELEASES.md b/RELEASES.md index 1db01fd5..70c7d183 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,6 +4,16 @@ Rhai Release Notes Version 0.19.7 ============== +Bug fixes +--------- + +* Fixes compilation errors with certain feature flag combinations. + +Enhancements +------------ + +* Property getters/setters and indexers defined in a plugin module are by default `#[rhai_fn(global)]`. + Version 0.19.6 ============== diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index caef2ba2..b1e26691 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -4,7 +4,8 @@ use quote::{quote, ToTokens}; use crate::attrs::ExportScope; use crate::function::{ - flatten_type_groups, print_type, ExportedFn, FnNamespaceAccess, FnSpecialAccess, + flatten_type_groups, print_type, ExportedFn, FnNamespaceAccess, FnSpecialAccess, FN_GET, + FN_IDX_GET, FN_IDX_SET, FN_SET, }; use crate::module::Module; @@ -81,7 +82,6 @@ pub(crate) fn generate_body( function.name().span(), ); let reg_names = function.exported_names(); - let mut namespace = FnNamespaceAccess::Internal; let fn_input_names: Vec = function .arg_list() @@ -141,11 +141,27 @@ pub(crate) fn generate_body( .map(print_type) .unwrap_or_else(|| "()".to_string()); - if let Some(ns) = function.params().namespace { - namespace = ns; - } - for fn_literal in reg_names { + let mut namespace = FnNamespaceAccess::Internal; + + match function.params().special { + FnSpecialAccess::None => {} + FnSpecialAccess::Index(_) | FnSpecialAccess::Property(_) => { + let reg_name = fn_literal.value(); + if reg_name.starts_with(FN_GET) + || reg_name.starts_with(FN_SET) + || reg_name == FN_IDX_GET + || reg_name == FN_IDX_SET + { + namespace = FnNamespaceAccess::Global; + } + } + } + + if let Some(ns) = function.params().namespace { + namespace = ns; + } + let ns_str = syn::Ident::new( match namespace { FnNamespaceAccess::Global => "Global", diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index 8c6bdd50..c586f0a1 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -1519,7 +1519,7 @@ mod generate_tests { } #[allow(unused_mut)] pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { - m.set_fn("get$square", FnNamespace::Internal, FnAccess::Public, Some(&["x: &mut u64", "u64"]), + m.set_fn("get$square", FnNamespace::Global, FnAccess::Public, Some(&["x: &mut u64", "u64"]), &[core::any::TypeId::of::()], int_foo_token().into()); if flatten {} else {} @@ -1597,7 +1597,7 @@ mod generate_tests { pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("square", FnNamespace::Internal, FnAccess::Public, Some(&["x: &mut u64", "u64"]), &[core::any::TypeId::of::()], int_foo_token().into()); - m.set_fn("get$square", FnNamespace::Internal, FnAccess::Public, Some(&["x: &mut u64", "u64"]), &[core::any::TypeId::of::()], + m.set_fn("get$square", FnNamespace::Global, FnAccess::Public, Some(&["x: &mut u64", "u64"]), &[core::any::TypeId::of::()], int_foo_token().into()); if flatten {} else {} } diff --git a/doc/src/patterns/enums.md b/doc/src/patterns/enums.md index 86f3504e..1d693564 100644 --- a/doc/src/patterns/enums.md +++ b/doc/src/patterns/enums.md @@ -37,7 +37,7 @@ mod MyEnumModule { MyEnum::Baz(val1, val2) } // Access to fields - #[rhai_fn(global, get = "enum_type")] + #[rhai_fn(get = "enum_type")] pub fn get_type(a: &mut MyEnum) -> String { match a { MyEnum::Foo => "Foo".to_string(), @@ -45,7 +45,7 @@ mod MyEnumModule { MyEnum::Baz(_, _) => "Baz".to_string() } } - #[rhai_fn(global, get = "field_0")] + #[rhai_fn(get = "field_0")] pub fn get_field_0(a: &mut MyEnum) -> Dynamic { match a { MyEnum::Foo => Dynamic::UNIT, @@ -53,7 +53,7 @@ mod MyEnumModule { MyEnum::Baz(x, _) => Dynamic::from(x) } } - #[rhai_fn(global, get = "field_1")] + #[rhai_fn(get = "field_1")] pub fn get_field_1(a: &mut MyEnum) -> Dynamic { match a { MyEnum::Foo | MyEnum::Bar(_) => Dynamic::UNIT, diff --git a/doc/src/patterns/events.md b/doc/src/patterns/events.md index 0312e42e..cdea89c5 100644 --- a/doc/src/patterns/events.md +++ b/doc/src/patterns/events.md @@ -54,6 +54,33 @@ struct Handler { } ``` +### Register API for Any Custom Type + +[Custom types] are often used to hold state. The easiest way to register an entire API is via a [plugin module]. + +```rust +use rhai::plugin::*; + +// A custom type to a hold state value. +#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] +pub struct SomeType { + data: i64; +} + +#[export_module] +mod SomeTypeAPI { + #[rhai_fn(global)] + pub func1(obj: &mut SomeType) -> bool { ... } + #[rhai_fn(global)] + pub func2(obj: &mut SomeType) -> bool { ... } + pub process(data: i64) -> i64 { ... } + #[rhai_fn(get = "value")] + pub get_value(obj: &mut SomeType) -> i64 { obj.data } + #[rhai_fn(set = "value")] + pub set_value(obj: &mut SomeType, value: i64) { obj.data = value; } +} +``` + ### Initialize Handler Object Steps to initialize the event handler: @@ -70,16 +97,10 @@ impl Handler { pub new(path: impl Into) -> Self { let mut engine = Engine::new(); - // Register API functions here + // Register custom types and API's engine - .register_fn("func1", func1) - .register_fn("func2", func2) - .register_fn("func3", func3) .register_type_with_name::("SomeType") - .register_get_set("value", - |obj: &mut SomeType| obj.data, - |obj: &mut SomeType, value: i64| obj.data = value - ); + .load_package(exported_module!(SomeTypeAPI)); // Create a custom 'Scope' to hold state let mut scope = Scope::new(); @@ -112,36 +133,30 @@ Mapping an event from the system into a scripted handler is straight-forward: impl Handler { // Say there are three events: 'start', 'end', 'update'. // In a real application you'd be handling errors... - pub fn on_event(&mut self, event_name: &str, event_data: i64) { + pub fn on_event(&mut self, event_name: &str, event_data: i64) -> Result<(), Error> { match event_name { // The 'start' event maps to function 'start'. // In a real application you'd be handling errors... - "start" => - self.engine - .call_fn(&mut self.scope, &self.ast, "start", (event_data,)).unwrap(), + "start" => self.engine.call_fn(&mut self.scope, &self.ast, "start", (event_data,))?, // The 'end' event maps to function 'end'. // In a real application you'd be handling errors... - "end" => - self.engine - .call_fn(&mut self.scope, &self.ast, "end", (event_data,)).unwrap(), + "end" => self.engine.call_fn(&mut self.scope, &self.ast, "end", (event_data,))?, // The 'update' event maps to function 'update'. // This event provides a default implementation when the scripted function // is not found. - "update" => - self.engine - .call_fn(&mut self.scope, &self.ast, "update", (event_data,)) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "update" => { - // Default implementation of 'update' event handler - self.scope.set_value("state2", SomeType::new(42)); - // Turn function-not-found into a success - Ok(Dynamic::UNIT) - } - _ => Err(err.into()) - }) - .unwrap() + "update" => self.engine + .call_fn(&mut self.scope, &self.ast, "update", (event_data,)) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "update" => { + // Default implementation of 'update' event handler + self.scope.set_value("state2", SomeType::new(42)); + // Turn function-not-found into a success + Ok(Dynamic::UNIT) + } + _ => Err(err.into()) + })? } } } @@ -159,24 +174,25 @@ fn start(data) { if state1 { throw "Already started!"; } - if func1(state2) || func2() { + if state2.func1() || state2.func2() { throw "Conditions not yet ready to start!"; } state1 = true; - state2.value = 0; + state2.value = data; } fn end(data) { if !state1 { throw "Not yet started!"; } - if func1(state2) || func2() { + if state2.func1() || state2.func2() { throw "Conditions not yet ready to start!"; } state1 = false; + state2.value = data; } fn update(data) { - state2.value += func3(data); + state2.value += process(data); } ``` diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index cbeb304d..a1e2a684 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -166,6 +166,9 @@ as well as all _type iterators_, are automatically exposed to the _global_ names [iteration]({{rootUrl}}/language/for.md), [getters/setters] and [indexers] for [custom types] can work as expected. +In fact, the default for all [getters/setters] and [indexers] defined in a plugin module +is `#[rhai_fn(global)]` unless specifically overridden by `#[rhai_fn(internal)]`. + Therefore, in the example above, the `increment` method (defined with `#[rhai_fn(global)]`) works fine when called in method-call style: