Skip to content

Commit 9276803

Browse files
authored
Merge pull request #22 from Bromeon/feature/rust-gdscript-exchange
Elaborate data representation and exchange between GDScript and Rust
2 parents c7929a4 + b844a82 commit 9276803

File tree

10 files changed

+694
-16
lines changed

10 files changed

+694
-16
lines changed

src/SUMMARY.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22

33
- [Introduction](./introduction.md)
44
- [Getting Started](./getting-started.md)
5-
- [Setup](./getting-started/setup.md)
6-
- [Hello, world!](./getting-started/hello-world.md)
5+
- [Setup](./getting-started/setup.md)
6+
- [Hello, world!](./getting-started/hello-world.md)
7+
- [An Overview of GDNative](./gdnative-overview.md)
8+
- [Data representations](gdnative-overview/data-representations.md)
9+
- [Ref, TRef and Instance -- wrappers for custom types](gdnative-overview/wrappers.md)
10+
- [Binding to Rust code](./rust-binding.md)
11+
- [Class registration](./rust-binding/classes.md)
12+
- [Exported methods](./rust-binding/methods.md)
13+
- [Exported properties](./rust-binding/properties.md)
14+
- [Calling into GDScript from Rust](./rust-binding/calling-gdscript.md)
715
- [FAQ](./faq.md)
8-
- [(TODO) An Overview of GDNative](./gdnative-overview.md)
916
- [(TODO) Testing](./testing.md)
10-
- [Structuring Code for Testing](./testing/structure.md)
11-
- [Testing with the Engine](./testing/engine.md)
17+
- [Structuring Code for Testing](./testing/structure.md)
18+
- [Testing with the Engine](./testing/engine.md)
1219
- [Exporting](./exporting.md)
1320
- [Android](./exporting/android.md)
1421
- [(TODO) iOS](./exporting/ios.md)

src/faq.md

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ It's relatively easy to work around this problem, though: Because of how the use
1616

1717
**Question**
1818

19-
A native script type needs to implement `fn new(owner: &Node) -> Self`.
19+
A native script type needs to implement `fn new(owner: &Node) -> Self`.
2020
Is it possible to pass additional arguments to `new`?
2121

2222
**Answer**
@@ -26,26 +26,66 @@ Unfortunately this is currently a general limitation of GDNative (see [related i
2626
As a result, a common pattern to work-around the limitation is to use explicit initialization methods. For instance:
2727

2828
```rust
29+
struct EnemyData {
30+
name: String,
31+
health: f32,
32+
}
33+
2934
#[derive(NativeClass)]
3035
#[inherit(Object)]
31-
struct DataWrapper {
32-
data: Option<Data>,
36+
struct Enemy {
37+
data: Option<EnemyData>,
3338
}
3439

35-
#[godot::methods]
36-
impl DataWrapper {
40+
#[methods]
41+
impl Enemy {
3742
fn new(_owner: &Object) -> Self {
38-
DataWrapper {
43+
Enemy {
3944
data: None,
4045
}
4146
}
4247

4348
#[export]
44-
fn set_data(&mut self, _owner: &Object, additional_arg1: i32, additional_arg2: i32) {
45-
self.data = Some(Data::new(additional_arg1, additional_arg2));
49+
fn set_data(&mut self, _owner: &Object, name: String, health: f32) {
50+
self.data = Some(EnemyData { name, health });
51+
}
52+
}
53+
```
54+
55+
This however has two disadvantages:
56+
1. You need to use an `Option` with the sole purpose of late initialization, and subsequent `unwrap()` calls or checks -- weaker invariants in short.
57+
1. An additional type `EnemyData` for each native class like `Enemy` is required (unless you have very few properties, or decide to add `Option` for each of them, which has its own disadvantages).
58+
59+
An alternative is to register a separate factory class, which returns fully-constructed instances:
60+
```rust
61+
#[derive(NativeClass)]
62+
#[no_constructor] // disallow default constructor
63+
#[inherit(Object)]
64+
struct Enemy {
65+
name: String,
66+
health: f32,
67+
}
68+
69+
#[methods]
70+
impl Enemy {
71+
// nothing here
72+
}
73+
74+
#[derive(NativeClass)]
75+
#[inherit(Reference)]
76+
struct EntityFactory {}
77+
78+
#[methods]
79+
impl EntityFactory {
80+
#[export]
81+
fn enemy(&self, _owner: &Object, name: String, health: f32)
82+
-> Instance<Enemy, Unique> {
83+
Enemy { name, health }.emplace()
4684
}
4785
}
4886
```
87+
So instead of `Enemy.new()` you can write `EntityFactory.enemy(args)` in GDScript.
88+
This still needs an extra type `EntityFactory`, however you could reuse that for multiple classes.
4989

5090

5191
## Static methods
@@ -68,7 +108,7 @@ As a work-around, it is possible to use a ZST (zero-sized type):
68108
#[inherit(Object)]
69109
pub struct StaticUtil;
70110

71-
#[godot::methods]
111+
#[methods]
72112
impl StaticUtil {
73113
#[export]
74114
fn compute_something(&self, _owner: &Object, input: i32) -> i32 {
@@ -100,7 +140,7 @@ How can I access the Rust type given the Variant?
100140
This conversion can be accomplished by casting the `Variant` to a `Ref`, and then to an `Instance` or `RefInstance`, and mapping over it to access the Rust data type:
101141

102142
```rust
103-
#[godot::methods]
143+
#[methods]
104144
impl AnotherNativeScript {
105145

106146
#[export]

src/gdnative-overview.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
# An Overview of GDNative
22

3-
TODO
3+
GDNative is the interface between the Godot engine and bindings in native languages, such as C, C++ or Rust.
4+
5+
This chapter gives a broad overview of basic GDNative concepts and godot-rust's approach to implement them in Rust. It is not a usage guide for exposing your Rust code to Godot; see chapter [Binding to Rust code](rust-binding.md) for concrete examples.
6+
7+
Subchapters:
8+
9+
1. [Data representations](gdnative-overview/data-representations.md)
10+
1. [`Ref`, `TRef` and `Instance`](gdnative-overview/wrappers.md)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Data representations
2+
3+
The godot-rust library uses many different approaches to store and transport data. This chapter explains high-level concepts of related terminology used throughout the library and its documentation. It is not a usage guide however -- to see the concepts in action, check out [Binding to Rust code](../rust-binding.md).
4+
5+
6+
## Object and class
7+
8+
Godot is built around _classes_, object-oriented types in a hierarchy, with the base class `Object` at the top. When talking about classes, we explicitly mean classes in the `Object` hierarchy and not built-in types like `String`, `Vector2`, `Color`, even though they are technically classes in C++. In Rust, classes are represented as structs.
9+
10+
Every user-defined class inherits `Object` directly or indirectly, and thus all methods defined in `Object` are accessible on _any_ instance of a user-defined class. This type includes functionality for:
11+
* object lifetime: `_init` (`new` in Rust), `free`
12+
* identification and printing: `to_string`, `get_instance_id`
13+
* reflection/introspection: `get_class`, `get`, `has_method`, ...
14+
* custom function invocation: `call`, `callv`, `call_deferred`
15+
* signal handling: `connect`, `emit_signal`, ...
16+
17+
`Object` itself comes with manual memory management. All instances must be deallocated using the `free()` method. This is typically not what you want, instead you will most often work with the following classes inherited from `Object`:
18+
19+
* **`Reference`**
20+
Reference-counted objects. This is the default base class if you don't use the `extends` keyword in GDScript. Allows to pass around instances of this type freely, managing memory automatically when the last reference goes out of scope.
21+
Do not confuse this type with the godot-rust `Ref` smart pointer.
22+
* **`Node`**
23+
Anything that's part of the scene tree, such as `Spatial` (3D), `CanvasItem` and `Node2D` (2D). Each node in the tree is responsible of its children and will deallocate them automatically when it is removed from the tree. At the latest, the entire tree will be destroyed when ending the application.
24+
**Important:** as long as a node is not attached to the scene tree, it behaves like an `Object` instance and must be freed manually. On the other hand, as long as it is part of the tree, it can be destroyed (e.g. when its parent is removed) and other references pointing to it become invalid.
25+
* **`Resource`**
26+
Data set that is loaded from disk and cached in memory, for example 3D meshes, materials, textures, fonts or music (see also [Godot tutorial](https://docs.godotengine.org/en/stable/getting_started/step_by_step/resources.html)).
27+
`Resource` inherits `Reference`, so in the context of godot-rust, it can be treated like a normal, reference-counted class.
28+
29+
When talking about inheritance, we always mean the relationship in GDScript code. Rust does not have inheritance, instead godot-rust implements `Deref` traits to allow implicit upcasts. This enables to invoke all parent methods and makes the godot-rust API very close to GDScript.
30+
31+
Classes need to be added as `NativeScript` resources inside the Godot editor, see [here](../getting-started/hello-world.html#creating-the-nativescript-resource) for a description.
32+
33+
_See `Object` in
34+
[godot-rust docs](https://docs.rs/gdnative/latest/gdnative/api/struct.Object.html),
35+
[Godot docs](https://docs.godotengine.org/en/latest/classes/class_object.html)_
36+
_See `GodotObject`, the Rust trait implemented for all Godot classes, in [godot-rust docs](https://docs.rs/gdnative/latest/gdnative/trait.GodotObject.html)_
37+
38+
39+
## Variant
40+
41+
`Variant` is a type that can hold an instance of _any_ type in Godot. This includes all classes (of type `Object`) as well as all built-in types such as `int`, `String`, `Vector2` etc.
42+
43+
Since GDScript is a dynamic language, you often deal with variants implicitly. Variables which are not type-annotated can have values of multiple types throughout their lifetime. In static languages like Rust, every value must have a defined type, thus untyped values in GDScript correspond to `Variant` in Rust. Godot APIs which accept any type as parameter are declared as `Variant` in the GDNative bindings (and thus godot-rust library). Sometimes, godot-rust also provides transparent mapping from/to concrete types behind the scenes.
44+
45+
Variants also have a second role as a serialization format between Godot and Rust. It is possible to extend this beyond the built-in Godot types. To make your own types convertible from and to variants, implement the traits [`FromVariant`](https://docs.rs/gdnative/latest/gdnative/core_types/trait.FromVariant.html) and [`ToVariant`](https://docs.rs/gdnative/latest/gdnative/core_types/trait.ToVariant.html). Types that can only be safely converted to variants by giving up ownership can use [OwnedToVariant](https://docs.rs/gdnative/0.9.3/gdnative/core_types/trait.OwnedToVariant.html), which is similar to the Rust `Into` trait.
46+
47+
_See `Variant` in
48+
[godot-rust docs](https://docs.rs/gdnative/latest/gdnative/core_types/struct.Variant.html),
49+
[Godot docs](https://docs.godotengine.org/en/latest/classes/class_variant.html)_
50+
51+
52+
## Script
53+
54+
Scripts are programmable building blocks that can be attached to nodes in the scene tree, in order to customize their behavior. Depending on the language in which the script is written, there are different classes which inherit the `Script` class; relevant here will be `NativeScript` for classes defined in Rust, and `GDScript` for classes defined in GDScript. Scripts are stored as Godot resources (like materials, textures, shaders etc), usually in their own separate file.
55+
56+
Scripts _always_ inherit another class from Godot's `Object` hierarchy, either an existing one from Godot or a user-defined one. In Rust, scripts are limited to inherit an existing Godot class; other scripts cannot be inherited. This makes each script a class on their own: they provide the properties and methods from their _base object_, plus all the properties and methods that you define in the script.
57+
58+
_See `Script` in
59+
[godot-rust docs](https://docs.rs/gdnative/latest/gdnative/api/struct.Script.html),
60+
[Godot docs](https://docs.godotengine.org/en/latest/classes/class_script.html)_

src/gdnative-overview/wrappers.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# `Ref`, `TRef` and `Instance`
2+
3+
Objects from Godot, such as scene nodes, materials, or other resources are owned and maintained by the Godot engine. This means that your Rust code will store references to existing objects, not values. godot-rust provides special wrapper types to deal with these references, which are explained in this page.
4+
5+
These classes stand in contrast to value types like `bool`, `int`, `Vector2`, `Color` etc., which are copied in GDScript, not referenced. In Rust, those types either map to built-in Rust types or structs implementing the `Copy` trait.
6+
7+
## `Ref`: persistent reference
8+
9+
The generic smart pointer `gdnative::Ref<T, Access>` allows you to store `Object` instances in Rust. It comes with different access policies, depending on how the memory of the underlying object is managed (consult [the docs](https://docs.rs/gdnative/latest/gdnative/struct.Ref.html) for details). Most of the time, you will be working with `Ref<T>`, which is the same as `Ref<T, Shared>` and the only access policy that is explained here. Its memory management mirrors that of the underlying type:
10+
* for all Godot objects inheriting the `Reference` class, `Ref<T>` is reference-counted like `Arc<T>` and will clean up automatically.
11+
* for all other types (i.e. the type `Object` and inheritors of `Node`), `Ref<T>` behaves like a raw pointer with manual memory management.
12+
13+
For example, storing a reference to a Godot `Node2D` instance in a struct would look as follows:
14+
```rust
15+
struct GodotNode {
16+
node_ref: Ref<Node2D>,
17+
}
18+
```
19+
20+
_See `Ref` in
21+
[godot-rust docs](https://docs.rs/gdnative/latest/gdnative/struct.Ref.html)_
22+
23+
24+
## `TRef`: temporary reference
25+
26+
While `Ref` is a persistent pointer to retain references to Godot objects for an extended period of time, it doesn't grant access to the underlying Godot object. The reason for this is that `Ref` cannot generally guarantee that the underlying object, which is managed by the Godot engine, is valid at the time of the access. However, you as a user are in control of GDScript code and the scene tree, thus you can assert that an object is valid at a certain point in time by using `assume_safe()`. This is an unsafe function that returns a `gdnative::TRef<T, Access>` object, which allows you to call methods on the node. You are responsible for this assumption to be correct; violating it can lead to undefined behavior.
27+
28+
The following example demonstrates `TRef`. A node is stored inside a Rust struct, and its position is modified through `set_position()`. This approach could be used in an ECS (Entity-Component-System) architecture, where `GodotNode` is a component, updated by a system.
29+
```rust
30+
struct GodotNode {
31+
node_ref: Ref<Node2D>,
32+
}
33+
34+
fn update_position(node: &GodotNode) {
35+
let pos = Vector2::new(20, 30);
36+
37+
// fetch temporary reference to the node
38+
let node: TRef<Node2D> = unsafe { node.node_ref.assume_safe() };
39+
40+
// call into the Godot engine
41+
// this implicitly invokes deref(), turning TRef<Node2D> into &Node2D
42+
node.set_position(pos);
43+
}
44+
```
45+
Note that the parameter type is `&GodotNode`, not `&mut GodotNode`. Then why is it possible to mutate the Godot object?
46+
47+
All Godot classes in Rust (`Object` and its subtypes) have only methods that operate on `&self`, not `&mut self`. The reason for this choice is that `&mut` is -- strictly speaking -- not a mutable reference, but rather an [_exclusive_ reference](https://docs.rs/dtolnay/latest/dtolnay/macro._02__reference_types.html). The one and only thing it guarantees is that while it exists, no other reference to the same object can exist (no aliasing). Since all the Godot classes can be shared with the Godot engine, which is written in C++ and allows free aliasing, using `&mut` references would potentially violate the exclusivity, leading to UB. This is why `&T` is used, and just like e.g. `&RefCell` it _does_ allow mutation.
48+
49+
This being said, it can still make sense to bring back some type safety on a higher level in your own code. For example, you could make the `update_position()` take a `&mut GodotNode` parameter, to make sure that access to this `GodotNode` object is exclusive.
50+
51+
52+
_See `TRef` in
53+
[godot-rust docs](https://docs.rs/gdnative/latest/gdnative/struct.TRef.html)_
54+
55+
56+
## `Instance`: reference with attached Rust class
57+
58+
When working with classes that are provided by the engine or defined in GDScript, the `Ref` smart pointer is the ideal type for interfacing between Rust and Godot. However, when defining a custom class in Rust, that is registered with the Godot engine, there are two parts that need to be stored together:
59+
60+
1. **GDNative script:** the Rust struct object that implements the entire custom logic. The Rust struct is written by you.
61+
1. **Base object:** the base class from which the script inherits, with its own state. This is always a Godot built-in class such as `Object`, `Reference` or `Node`.
62+
63+
The `Instance` class simply wraps the two parts into a single type.
64+
65+
When passing around your own Rust types, you will thus be working with `Instance`. The traits `ToVariant`, `FromVariant` and `OwnedToVariant` are automatically implemented for `Instance` types, allowing you to pass them from and to the Godot engine.
66+
67+
68+
### Construction
69+
70+
Let's use a straightforward example: a player with name and score. Exported methods and properties are omitted for simplicity; the full interfacing will be explained later in [Calling into GDScript from Rust](../rust-binding/calling-gdscript.md).
71+
```rust
72+
#[derive(NativeClass)]
73+
// no #[inherit], thus inherits Reference by default
74+
pub struct Player {
75+
name: String,
76+
score: u32,
77+
}
78+
79+
#[methods]
80+
impl Player {
81+
fn new(_owner: &Reference) -> Self {
82+
Self {
83+
name: "New player".to_string(),
84+
score: 0
85+
}
86+
}
87+
}
88+
```
89+
90+
To create a default instance, use `Instance::new_instance()`.
91+
You can later use `map()` and `map_mut()` to access the `Instance` immutably and mutably.
92+
93+
```rust
94+
let instance: Instance<Reference, Unique> = Instance::new();
95+
// or:
96+
let instance = Player::new_instance();
97+
98+
// note: map_mut() takes &self, so above is not 'let mut'
99+
instance.map_mut(|p: &mut Player, _base: TRef<Reference, Unique>| {
100+
p.name = "Joe".to_string();
101+
p.score = 120;
102+
});
103+
```
104+
105+
If you don't need a Godot-enabled default constructor, use the `#[no_constructor]` attribute and define your own Rust `new()` constructor.
106+
```rust
107+
#[derive(NativeClass)]
108+
#[no_constructor]
109+
pub struct Player {
110+
name: String,
111+
score: u32,
112+
}
113+
114+
#[methods]
115+
impl Player {
116+
pub fn new(name: &str, score: u32) -> Self {
117+
Self { name: name.to_string(), score }
118+
}
119+
}
120+
```
121+
122+
In this case, you can construct an `Instance` from an existing Rust object using `Instance::emplace()`:
123+
```rust
124+
let player = Player::new("Joe", 120);
125+
126+
let instance = Instance::emplace(player);
127+
// or:
128+
let instance = player.emplace();
129+
```
130+
131+
132+
133+
134+
_See `Instance` in
135+
[godot-rust docs](https://docs.rs/gdnative/latest/gdnative/nativescript/struct.Instance.html)_
136+

src/rust-binding.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Binding to Rust code
2+
3+
This chapter provides an exhaustive list of mechanisms to pass data through the Rust GDNative binding, in both directions:
4+
* **GDScript -> Rust**, e.g. to react to an input event with custom Rust logic
5+
* **Rust -> GDScript**, e.g. to apply a game logic change to a graphics node in Godot
6+
7+
The goal is to serve as both an in-depth learning resource for newcomers and a reference to look up specific mechanisms at a later stage. Before delving into this chapter, make sure to read [An Overview of GDNative](gdnative-overview.md), which explains several fundamental concepts used here.
8+
9+
The subchapters are intended to be read in order, but you can navigate to them directly:
10+
11+
1. [Class registration](rust-binding/classes.md)
12+
1. [Exported methods](rust-binding/methods.md)
13+
1. [Exported properties](rust-binding/properties.md)
14+
1. [Calling into GDScript from Rust](./rust-binding/calling-gdscript.md)

0 commit comments

Comments
 (0)