Skip to content

Commit 6c38d68

Browse files
authored
ClassBuilder, ToVariant/FromVariant and Export explanations. (#51)
* 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 * PR Fixes
1 parent 82cb366 commit 6c38d68

File tree

3 files changed

+245
-0
lines changed

3 files changed

+245
-0
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- [Class registration](./rust-binding/classes.md)
1313
- [Exported methods](./rust-binding/methods.md)
1414
- [Exported properties](./rust-binding/properties.md)
15+
- [ToVariant, FromVariant and Export](./rust-binding/to-variant-from-variant-export.md)
1516
- [Calling into GDScript from Rust](./rust-binding/calling-gdscript.md)
1617
- [FAQ](./faq.md)
1718
- [Common code questions](./faq/code.md)

src/rust-binding/properties.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,71 @@ impl GodotApi {
8686
}
8787
}
8888
```
89+
90+
## Manual property registration
91+
92+
For cases not covered by the `#[property]` attribute, it may be necessary to manually register the properties instead.
93+
94+
This is often the case where custom hint behavior is desired for primitive types, such as an Integer value including an `IntEnum` hint.
95+
96+
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.
97+
98+
```rust
99+
#[derive(NativeClass)]
100+
#[inherit(gdnative::api::Node)]
101+
#[register_with(Self::register_properties)]
102+
pub struct MyNode {
103+
number: i32,
104+
number_enum: i32,
105+
float_range: f32,
106+
my_filepath: String,
107+
}
108+
109+
#[gdnative::methods]
110+
impl MyNode {
111+
fn register_properties(builder: &ClassBuilder<MyNode>) {
112+
use gdnative::nativescript::property::StringHint;
113+
// Add a number with a getter and setter.
114+
// (This is the equivalent of adding the `#[property]` attribute for `number`)
115+
builder
116+
.add_property::<i32>("number")
117+
.with_getter(number_getter)
118+
.with_setter(numer_setter)
119+
.done();
120+
121+
// Register the number as an Enum
122+
builder
123+
.add_property::<i32>("number_enum")
124+
.with_getter(move |my_node: &MyNode, _owner: TRef<Node>| my_node.number_enum)
125+
.with_setter(move |my_node: &mut MyNode, _owner: TRef<Node>, new_value| my_node.number_enum = new_value)
126+
.with_default(1)
127+
.with_hint(IntHint::Enum(EnumHint::new("a", "b", "c", "d")))
128+
.done();
129+
130+
// Register a floating point value with a range from 0.0 to 100.0 with a step of 0.1
131+
builder
132+
.add_property::<f64>("float_range")
133+
.with_getter(move |my_node: &MyNode, _owner: TRef<Node>| my_node.float_range)
134+
.with_setter(move |my_node: &mut MyNode, _owner: TRef<Node>, new_value| my_node.float_range = new_value)
135+
.with_default(1.0)
136+
.with_hint(FloatHint::Range(RangeHint::new(0.0, 100.0).with_step(0.1)))
137+
.done();
138+
139+
// Manually register a string as a file path for .txt and .dat files.
140+
builder
141+
.add_property::<String>("my_filepath")
142+
.with_getter(move |my_node: &MyNode, _owner: TRef<Node>| my_node.my_filepath.clone())
143+
.with_setter(move |my_node: &mut MyNode, _owner: TRef<Node>, new_value: String| my_node.my_filepath = new_value)
144+
.with_default("".to_owned())
145+
.with_hint(StringHint::File(EnumHint::new(vec!["*.txt".to_owned(), "*.dat".to_owned()])))
146+
.done();
147+
}
148+
fn number_getter(&self, _owner: TRef<Node>) -> i32 {
149+
self.number
150+
}
151+
152+
fn number_setter(&mut self, _owner: TRef<Node>, new_value: i32) {
153+
self.number = new_value
154+
}
155+
}
156+
```
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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

Comments
 (0)