From 1581374f26adf37465773da9de3551482cacfb76 Mon Sep 17 00:00:00 2001 From: Jacobsky Date: Sat, 30 Oct 2021 13:12:20 +0900 Subject: [PATCH 1/2] ClassBuilder, ToVariant/FromVariant and Export explanations. To resolve Issue #38 I have added some entries in `rust-bindings/properties.md` In addition, I added a section that can better explain the `ToVariant`, `FromVariant` and `Export` traits as these are going to end up being really common issues for folks that work heavily with the Godot Editor --- src/SUMMARY.md | 1 + src/rust-binding/properties.md | 65 +++++++ .../to-variant-from-variant-export.md | 175 ++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 src/rust-binding/to-variant-from-variant-export.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 742cc8c..4cceb67 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -12,6 +12,7 @@ - [Class registration](./rust-binding/classes.md) - [Exported methods](./rust-binding/methods.md) - [Exported properties](./rust-binding/properties.md) + - [ToVariant, FromVariant and Export](./rust-binding/to-variant-from-variant-export.md) - [Calling into GDScript from Rust](./rust-binding/calling-gdscript.md) - [FAQ](./faq.md) - [Common code questions](./faq/code.md) diff --git a/src/rust-binding/properties.md b/src/rust-binding/properties.md index f7e26ce..da8c46f 100644 --- a/src/rust-binding/properties.md +++ b/src/rust-binding/properties.md @@ -86,3 +86,68 @@ impl GodotApi { } } ``` + +## Manual Property Registration + +For cases not covered by the `#[property]` attribute, it may be necessary to manually register the properties instead. + +This is often the case where custom hint behaivor is desired for primitive types, such as an Integer value including an `IntEnum` hint. + +To do so, you can use the [`ClassBuilder`](https://docs.rs/gdnative/0.9.3/gdnative/prelude/struct.ClassBuilder.html) -- such as in the following examples -- to manually register each property and customize how they interface in the editor. + +```rust +#[derive(NativeClass)] +#[inherit(gdnative::api::Node)] +#[register_with(Self::register_properties)] +pub struct MyNode { + number: i32, + number_enum: i32, + float_range: f32, + my_filepath: String, +} + +#[gdnative::methods] +impl MyNode { + fn register_properties(builder: &ClassBuilder) { + use gdnative::nativescript::property::StringHint; + // Add a number with a getter and setter. + // (This is the equivalent of adding the `#[property]` attribute for `number`) + builder + .add_property::("number") + .with_getter(number_getter) + .with_setter(numer_setter) + .done(); + // Register the number as an Enum + builder + .add_property::("number_enum") + .with_getter(move |my_node: &MyNode, _owner: TRef| my_node.number_enum) + .with_setter(move |my_node: &mut MyNode, _owner: TRef, new_value| my_node.number_enum = new_value) + .with_default(1) + .with_hint(IntHint::Enum(EnumHint::new("a", "b", "c", "d"))) + .done(); + // Register a floating point value with a range from 0.0 to 100.0 with a step of 0.1 + builder + .add_property::("float_range") + .with_getter(move |my_node: &MyNode, _owner: TRef| my_node.float_range) + .with_setter(move |my_node: &mut MyNode, _owner: TRef, new_value| my_node.float_range = new_value) + .with_default(1.0) + .with_hint(FloatHint::Range(RangeHint::new(0.0, 100.0).with_step(0.1))) + .done(); + // Manually register a string as a file path for .txt and .dat files. + builder + .add_property::("my_filepath") + .with_getter(move |my_node: &MyNode, _owner: TRef| my_node.my_filepath.clone()) + .with_setter(move |my_node: &mut MyNode, _owner: TRef, new_value: String| my_node.my_filepath = new_value) + .with_default("".to_owned()) + .with_hint(StringHint::File(EnumHint::new(vec!["*.txt".to_owned(), "*.dat".to_owned()]))) + .done(); + } + fn number_getter(&self, _owner: TRef) -> i32 { + self.number + } + + fn number_setter(&mut self, _owner: TRef, new_value: i32) { + self.number = new_value + } +} +``` \ No newline at end of file diff --git a/src/rust-binding/to-variant-from-variant-export.md b/src/rust-binding/to-variant-from-variant-export.md new file mode 100644 index 0000000..79d0bc8 --- /dev/null +++ b/src/rust-binding/to-variant-from-variant-export.md @@ -0,0 +1,175 @@ +# ToVariant, FromVariant and Export + +As seen in [the previous section](./properties.md), the `#[property]` attribute of the [NativeClass] procedural macro is a powerful tool for to automatically configuring properties with your Godot. + +One constraint of the `#[property]` attribute is that it requires that all attributed property types implement `ToVariant`, `FromVariant` and `Export` in order to interface with Godot. + +## ToVariant/FromVariant traits + +In Godot all types inherit from Variant. + +As per the [official Godot docs](https://docs.godotengine.org/en/stable/classes/class_variant.html), Variant is "The most important data type in Godot." This is a wrapper type that can store any [Godot Engine type](https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#enum-globalscope-variant-type) + +The ToVariant and FromVariant are conversion traits that allow Rust types to be converted between these types. All properties must implement both ToVariant and FromVariant while exported methods require `FromVariant` to be implemented for optional parameters and `ToVariant` to be implemented for return types. + +For many datatypes, it is possible to use the derive macros such as in the following example, + +```rust +// Note: This struct does not implement `export` and cannot be used a property, see the following section for more information. +#[derive(ToVariant, FromVariant)] +struct Foo { + number: i32, + float: f32, + string: String +} +``` + +For more information about how you can customize the behavior of the dervive macros, please refer to the official documentation for the latest information. + +- [ToVariant](https://docs.rs/gdnative/latest/gdnative/core_types/trait.ToVariant.html) +- [FromVariant](https://docs.rs/gdnative/latest/gdnative/core_types/trait.FromVariant.html) + +## Export Trait + +The Godot editor retrieves property information from [Object::get_property_list](https://docs.godotengine.org/en/stable/classes/class_object.html#id2). To populate this data, `godot-rust` requires that the [`Export`](https://docs.rs/gdnative/0.9.3/gdnative/nativescript/trait.Export.html) trait be implemented for each type Rust struct. + +There are no derive macros that can be used for `Export` but many of the primitive types have it implemented by default. + +To implement `Export` for the previous Rust data type, you can do so as in the following example: + +```rust +// Note: By default `struct` will be converted to and from a Dictionary where property corresponds to a key-value pair. +#[derive(ToVariant, FromVariant)] +struct Foo { + number: i32, + float: f32, + string: String +} + +impl Export for Foo { + // This type should normally be one of the types defined in [gdnative::nativescript::hint](https://docs.rs/gdnative/0.9.3/gdnative/nativescript/init/property/hint/index.html). + // Or it can be any custom type for differentiating the hint types. + // In this case it is unused, so it is left as () + type Hint = (); + fn export_info(hint: Option) -> ExportInfo { + // As `Foo` is a struct that will be converted to a Dictionary when converted to a variant, we can just add this as the VariantType. + ExportInfo::new(VariantType::Dictionary) + } +} +``` + +## Case Study: Exporting Rust enums to Godot and Back. + +A common challenge that many developers may encounter when using godot-rust is that while [Rust enums](https://doc.rust-lang.org/std/keyword.enum.html) are [Algebraic Data Types](https://en.wikipedia.org/wiki/Algebraic_data_type), [Godot enums](https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html#enums) are constants that correspond to integer types. + +By default, Rust enums are converted to a Dictionary representation with keys corresponding to each enum variant where the Enum Value corresponds to a Dictionary that contains the key-value pairs that corresponding to each propery. + +For example: + +```rust +#[derive(ToVariant, FromVariant)] +enum MyEnum { + A, + B { inner: i32 }, + C { inner: String} +} +``` + +Will convert to a the following dictionary + +```gdscript +# MyEnum::A +"{ "A": {}} +# MyEnum::B { inner: 0} +{ "B": { "inner": 0 }} +# MyEnum::C { inner: "value" } +{ "C": {"inner": "value" }} +``` + +As of writing (gdnative 0.9.3), this default case is not configurable. If you want different behavior, it is necessary to implement `FromVariant` and `Export` manually for this data-type. + +### Case 1: Rust Enum -> Godot Enum + +Consider the following code + +```rust +enum MyIntEnum { + A=0, B=1, C=2, +} + +#[derive(NativeClass)] +#[inherit(Node)] +#[no_constructor] +struct MyNode { + #[export] + int_enum: MyIntEnum +} +``` + +This defines a simple enum where each enum value refers to an integer value + +If you were to try to add this to a NativeClass as a follows property you will receive errors similar to the following: + +```sh +the trait bound `MyIntEnum: gdnative::prelude::FromVariant` is not satisfied + required because of the requirements on the impl of `property::accessor::RawSetter` for `property::accessor::invalid::InvalidSetter<'_>`2 + +the trait bound `MyIntEnum: Export` is not satisfied + the trait `Export` is not implemented for `MyIntEnum` +``` + +This indicates that `MyIntEnum` does not have the necessary traits implemented for `FromVariant` and `Export`. Since the default derived behavior may not be quite what we want, we can implement this with the following: + +```rust +impl FromVariant for MyIntEnum { + fn from_variant(variant: &Variant) -> Result { + let result = i64::from_variant(variant)?; + match result { + 0 => Ok(MyIntEnum::A), + 1 => Ok(MyIntEnum::B), + 2 => Ok(MyIntEnum::C), + _ => Err(FromVariantError::UnknownEnumVariant { + variant: "i64".to_owned(), + expected: &["0", "1", "2"], + }), + } + } +} + +impl Export for MyIntEnum { + type Hint = IntHint; + + fn export_info(_hint: Option) -> ExportInfo { + Self::Hint::Enum(EnumHint::new(vec![ + "A".to_owned(), + "B".to_owned(), + "C".to_owned(), + ])) + .export_info() + } +} + +``` + +After implementing export, you would also receive the following error: + +```sh +the trait bound `MyIntEnum: gdnative::prelude::ToVariant` is not satisfied +the trait `gdnative::prelude::ToVariant` is not implemented for `MyIntEnum` +``` + +If the default implementation were sufficient, we could use `#[derive(ToVariant)]` for `MyIntEnum` or implement this as follows: + +```rust +impl ToVariant for MyIntEnum { + fn to_variant(&self) -> Variant { + match self { + MyIntEnum::A => { Variant::from_i64(0) }, + MyIntEnum::B => { Variant::from_i64(1) }, + MyIntEnum::C => { Variant::from_i64(2) }, + } + } +} +``` + +At this point, there should be no problem in using `MyIntEnum` as a property in your native class that is exported to the editor. From 03f798b3f97827714dc9143ab99ad596dd13d321 Mon Sep 17 00:00:00 2001 From: Jacobsky Date: Thu, 18 Nov 2021 21:52:14 +0900 Subject: [PATCH 2/2] PR Fixes --- src/rust-binding/properties.md | 7 +++- .../to-variant-from-variant-export.md | 41 ++++++++++--------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/rust-binding/properties.md b/src/rust-binding/properties.md index da8c46f..c5fc238 100644 --- a/src/rust-binding/properties.md +++ b/src/rust-binding/properties.md @@ -87,11 +87,11 @@ impl GodotApi { } ``` -## Manual Property Registration +## Manual property registration For cases not covered by the `#[property]` attribute, it may be necessary to manually register the properties instead. -This is often the case where custom hint behaivor is desired for primitive types, such as an Integer value including an `IntEnum` hint. +This is often the case where custom hint behavior is desired for primitive types, such as an Integer value including an `IntEnum` hint. To do so, you can use the [`ClassBuilder`](https://docs.rs/gdnative/0.9.3/gdnative/prelude/struct.ClassBuilder.html) -- such as in the following examples -- to manually register each property and customize how they interface in the editor. @@ -117,6 +117,7 @@ impl MyNode { .with_getter(number_getter) .with_setter(numer_setter) .done(); + // Register the number as an Enum builder .add_property::("number_enum") @@ -125,6 +126,7 @@ impl MyNode { .with_default(1) .with_hint(IntHint::Enum(EnumHint::new("a", "b", "c", "d"))) .done(); + // Register a floating point value with a range from 0.0 to 100.0 with a step of 0.1 builder .add_property::("float_range") @@ -133,6 +135,7 @@ impl MyNode { .with_default(1.0) .with_hint(FloatHint::Range(RangeHint::new(0.0, 100.0).with_step(0.1))) .done(); + // Manually register a string as a file path for .txt and .dat files. builder .add_property::("my_filepath") diff --git a/src/rust-binding/to-variant-from-variant-export.md b/src/rust-binding/to-variant-from-variant-export.md index 79d0bc8..5d94270 100644 --- a/src/rust-binding/to-variant-from-variant-export.md +++ b/src/rust-binding/to-variant-from-variant-export.md @@ -1,6 +1,6 @@ # ToVariant, FromVariant and Export -As seen in [the previous section](./properties.md), the `#[property]` attribute of the [NativeClass] procedural macro is a powerful tool for to automatically configuring properties with your Godot. +As seen in [the previous section](./properties.md), the `#[property]` attribute of the [`NativeClass`](https://docs.rs/gdnative/latest/gdnative/derive.NativeClass.html) procedural macro is a powerful tool to automatically configure properties with Godot. One constraint of the `#[property]` attribute is that it requires that all attributed property types implement `ToVariant`, `FromVariant` and `Export` in order to interface with Godot. @@ -10,12 +10,12 @@ In Godot all types inherit from Variant. As per the [official Godot docs](https://docs.godotengine.org/en/stable/classes/class_variant.html), Variant is "The most important data type in Godot." This is a wrapper type that can store any [Godot Engine type](https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#enum-globalscope-variant-type) -The ToVariant and FromVariant are conversion traits that allow Rust types to be converted between these types. All properties must implement both ToVariant and FromVariant while exported methods require `FromVariant` to be implemented for optional parameters and `ToVariant` to be implemented for return types. +The `ToVariant` and `FromVariant` are conversion traits that allow Rust types to be converted between these types. All properties must implement both `ToVariant` and `FromVariant` while exported methods require `FromVariant` to be implemented for optional parameters and `ToVariant` to be implemented for return types. -For many datatypes, it is possible to use the derive macros such as in the following example, +For many datatypes, it is possible to use the derive macros such as in the following example: ```rust -// Note: This struct does not implement `export` and cannot be used a property, see the following section for more information. +// Note: This struct does not implement `Export` and cannot be used as a property, see the following section for more information. #[derive(ToVariant, FromVariant)] struct Foo { number: i32, @@ -58,11 +58,11 @@ impl Export for Foo { } ``` -## Case Study: Exporting Rust enums to Godot and Back. +## Case study: exporting Rust enums to Godot and back A common challenge that many developers may encounter when using godot-rust is that while [Rust enums](https://doc.rust-lang.org/std/keyword.enum.html) are [Algebraic Data Types](https://en.wikipedia.org/wiki/Algebraic_data_type), [Godot enums](https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html#enums) are constants that correspond to integer types. -By default, Rust enums are converted to a Dictionary representation with keys corresponding to each enum variant where the Enum Value corresponds to a Dictionary that contains the key-value pairs that corresponding to each propery. +By default, Rust enums are converted to a Dictionary representation. Its keys correspond to the name of the enum variants, while the values correspond to a Dictionary with fields as key-value pairs. For example: @@ -71,26 +71,26 @@ For example: enum MyEnum { A, B { inner: i32 }, - C { inner: String} + C { inner: String } } ``` -Will convert to a the following dictionary +Will convert to the following dictionary: ```gdscript # MyEnum::A -"{ "A": {}} -# MyEnum::B { inner: 0} -{ "B": { "inner": 0 }} +"{ "A": {} } +# MyEnum::B { inner: 0 } +{ "B": { "inner": 0 } } # MyEnum::C { inner: "value" } -{ "C": {"inner": "value" }} +{ "C": {"inner": "value" } } ``` As of writing (gdnative 0.9.3), this default case is not configurable. If you want different behavior, it is necessary to implement `FromVariant` and `Export` manually for this data-type. ### Case 1: Rust Enum -> Godot Enum -Consider the following code +Consider the following code: ```rust enum MyIntEnum { @@ -106,9 +106,9 @@ struct MyNode { } ``` -This defines a simple enum where each enum value refers to an integer value +This code defines the enum `MyIntEnum`, where each enum value refers to an integer value. -If you were to try to add this to a NativeClass as a follows property you will receive errors similar to the following: +Without implementing the `FromVariant` and `Export` traits, attempting to export `MyIntEnum` as a property of `MyNode` will result in the following error: ```sh the trait bound `MyIntEnum: gdnative::prelude::FromVariant` is not satisfied @@ -151,22 +151,23 @@ impl Export for MyIntEnum { ``` -After implementing export, you would also receive the following error: +After implementing `FromVariant` and `Export`, running cargo check wouuld result in the following additional error: ```sh the trait bound `MyIntEnum: gdnative::prelude::ToVariant` is not satisfied the trait `gdnative::prelude::ToVariant` is not implemented for `MyIntEnum` ``` -If the default implementation were sufficient, we could use `#[derive(ToVariant)]` for `MyIntEnum` or implement this as follows: +If the default implementation were sufficient, we could use `#[derive(ToVariant)]` for `MyIntEnum` or implement it manually with the following code: ```rust +use gdnative::core_types::ToVariant; impl ToVariant for MyIntEnum { fn to_variant(&self) -> Variant { match self { - MyIntEnum::A => { Variant::from_i64(0) }, - MyIntEnum::B => { Variant::from_i64(1) }, - MyIntEnum::C => { Variant::from_i64(2) }, + MyIntEnum::A => { 0.to_variant() }, + MyIntEnum::B => { 1.to_variant() }, + MyIntEnum::C => { 2.to_variant() }, } } }