|
| 1 | +# ToVariant, FromVariant and Export |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +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. |
| 6 | + |
| 7 | +## ToVariant/FromVariant traits |
| 8 | + |
| 9 | +In Godot all types inherit from Variant. |
| 10 | + |
| 11 | +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) |
| 12 | + |
| 13 | +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. |
| 14 | + |
| 15 | +For many datatypes, it is possible to use the derive macros such as in the following example: |
| 16 | + |
| 17 | +```rust |
| 18 | +// Note: This struct does not implement `Export` and cannot be used as a property, see the following section for more information. |
| 19 | +#[derive(ToVariant, FromVariant)] |
| 20 | +struct Foo { |
| 21 | + number: i32, |
| 22 | + float: f32, |
| 23 | + string: String |
| 24 | +} |
| 25 | +``` |
| 26 | + |
| 27 | +For more information about how you can customize the behavior of the dervive macros, please refer to the official documentation for the latest information. |
| 28 | + |
| 29 | +- [ToVariant](https://docs.rs/gdnative/latest/gdnative/core_types/trait.ToVariant.html) |
| 30 | +- [FromVariant](https://docs.rs/gdnative/latest/gdnative/core_types/trait.FromVariant.html) |
| 31 | + |
| 32 | +## Export Trait |
| 33 | + |
| 34 | +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. |
| 35 | + |
| 36 | +There are no derive macros that can be used for `Export` but many of the primitive types have it implemented by default. |
| 37 | + |
| 38 | +To implement `Export` for the previous Rust data type, you can do so as in the following example: |
| 39 | + |
| 40 | +```rust |
| 41 | +// Note: By default `struct` will be converted to and from a Dictionary where property corresponds to a key-value pair. |
| 42 | +#[derive(ToVariant, FromVariant)] |
| 43 | +struct Foo { |
| 44 | + number: i32, |
| 45 | + float: f32, |
| 46 | + string: String |
| 47 | +} |
| 48 | + |
| 49 | +impl Export for Foo { |
| 50 | + // 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). |
| 51 | + // Or it can be any custom type for differentiating the hint types. |
| 52 | + // In this case it is unused, so it is left as () |
| 53 | + type Hint = (); |
| 54 | + fn export_info(hint: Option<Self::Hint>) -> ExportInfo { |
| 55 | + // 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. |
| 56 | + ExportInfo::new(VariantType::Dictionary) |
| 57 | + } |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +## Case study: exporting Rust enums to Godot and back |
| 62 | + |
| 63 | +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. |
| 64 | + |
| 65 | +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. |
| 66 | + |
| 67 | +For example: |
| 68 | + |
| 69 | +```rust |
| 70 | +#[derive(ToVariant, FromVariant)] |
| 71 | +enum MyEnum { |
| 72 | + A, |
| 73 | + B { inner: i32 }, |
| 74 | + C { inner: String } |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +Will convert to the following dictionary: |
| 79 | + |
| 80 | +```gdscript |
| 81 | +# MyEnum::A |
| 82 | +"{ "A": {} } |
| 83 | +# MyEnum::B { inner: 0 } |
| 84 | +{ "B": { "inner": 0 } } |
| 85 | +# MyEnum::C { inner: "value" } |
| 86 | +{ "C": {"inner": "value" } } |
| 87 | +``` |
| 88 | + |
| 89 | +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. |
| 90 | + |
| 91 | +### Case 1: Rust Enum -> Godot Enum |
| 92 | + |
| 93 | +Consider the following code: |
| 94 | + |
| 95 | +```rust |
| 96 | +enum MyIntEnum { |
| 97 | + A=0, B=1, C=2, |
| 98 | +} |
| 99 | + |
| 100 | +#[derive(NativeClass)] |
| 101 | +#[inherit(Node)] |
| 102 | +#[no_constructor] |
| 103 | +struct MyNode { |
| 104 | + #[export] |
| 105 | + int_enum: MyIntEnum |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +This code defines the enum `MyIntEnum`, where each enum value refers to an integer value. |
| 110 | + |
| 111 | +Without implementing the `FromVariant` and `Export` traits, attempting to export `MyIntEnum` as a property of `MyNode` will result in the following error: |
| 112 | + |
| 113 | +```sh |
| 114 | +the trait bound `MyIntEnum: gdnative::prelude::FromVariant` is not satisfied |
| 115 | + required because of the requirements on the impl of `property::accessor::RawSetter<MyNode, MyIntEnum>` for `property::accessor::invalid::InvalidSetter<'_>`2 |
| 116 | +
|
| 117 | +the trait bound `MyIntEnum: Export` is not satisfied |
| 118 | + the trait `Export` is not implemented for `MyIntEnum` |
| 119 | +``` |
| 120 | +
|
| 121 | +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: |
| 122 | +
|
| 123 | +```rust |
| 124 | +impl FromVariant for MyIntEnum { |
| 125 | + fn from_variant(variant: &Variant) -> Result<Self, FromVariantError> { |
| 126 | + let result = i64::from_variant(variant)?; |
| 127 | + match result { |
| 128 | + 0 => Ok(MyIntEnum::A), |
| 129 | + 1 => Ok(MyIntEnum::B), |
| 130 | + 2 => Ok(MyIntEnum::C), |
| 131 | + _ => Err(FromVariantError::UnknownEnumVariant { |
| 132 | + variant: "i64".to_owned(), |
| 133 | + expected: &["0", "1", "2"], |
| 134 | + }), |
| 135 | + } |
| 136 | + } |
| 137 | +} |
| 138 | +
|
| 139 | +impl Export for MyIntEnum { |
| 140 | + type Hint = IntHint<u32>; |
| 141 | +
|
| 142 | + fn export_info(_hint: Option<Self::Hint>) -> ExportInfo { |
| 143 | + Self::Hint::Enum(EnumHint::new(vec![ |
| 144 | + "A".to_owned(), |
| 145 | + "B".to_owned(), |
| 146 | + "C".to_owned(), |
| 147 | + ])) |
| 148 | + .export_info() |
| 149 | + } |
| 150 | +} |
| 151 | +
|
| 152 | +``` |
| 153 | +
|
| 154 | +After implementing `FromVariant` and `Export`, running cargo check wouuld result in the following additional error: |
| 155 | +
|
| 156 | +```sh |
| 157 | +the trait bound `MyIntEnum: gdnative::prelude::ToVariant` is not satisfied |
| 158 | +the trait `gdnative::prelude::ToVariant` is not implemented for `MyIntEnum` |
| 159 | +``` |
| 160 | +
|
| 161 | +If the default implementation were sufficient, we could use `#[derive(ToVariant)]` for `MyIntEnum` or implement it manually with the following code: |
| 162 | +
|
| 163 | +```rust |
| 164 | +use gdnative::core_types::ToVariant; |
| 165 | +impl ToVariant for MyIntEnum { |
| 166 | + fn to_variant(&self) -> Variant { |
| 167 | + match self { |
| 168 | + MyIntEnum::A => { 0.to_variant() }, |
| 169 | + MyIntEnum::B => { 1.to_variant() }, |
| 170 | + MyIntEnum::C => { 2.to_variant() }, |
| 171 | + } |
| 172 | + } |
| 173 | +} |
| 174 | +``` |
| 175 | +
|
| 176 | +At this point, there should be no problem in using `MyIntEnum` as a property in your native class that is exported to the editor. |
0 commit comments