Skip to content

Commit 197833a

Browse files
authored
Merge pull request #68 from godot-rust/qol/objects
Add explanation of `bind`, `bind_mut`, `base`, `base_mut`
2 parents 20ec8a0 + a05604d commit 197833a

File tree

4 files changed

+212
-21
lines changed

4 files changed

+212
-21
lines changed

ReadMe.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
# The godot-rust book
22

33
The godot-rust book is a user guide for **gdext**, the Rust bindings to Godot 4.
4-
The book is still work-in-progress, and contributions are very welcome.
4+
It covers a large part of the concepts and complements [the API docs][gdext-docs].
5+
There is also [gdnative-book] for Godot 3.
56

6-
An online version of the book is available at [godot-rust.github.io/book][book-web].
7-
For the gdnative book, check out [gdnative-book].
7+
> [!Tip]
8+
> The book is deployed at **[godot-rust.github.io/book][book-web]**.
9+
10+
11+
## Local setup
812

913
The book is built with [mdBook] and the plugins [mdbook-toc] and [mdbook-admonish]. To install them and build the book locally, you can run:
1014

@@ -20,20 +24,23 @@ mdbook serve --open
2024
```
2125

2226

23-
## Formatting and linting
27+
### Formatting and linting
2428

25-
We use [markdownlint] to enforce a consistent style across the Markdown files.
29+
[markdownlint] enforces a consistent style across the Markdown files.
2630
It is automatically run during CI, but if you have the `npm` toolchain, you can also run it locally:
2731

2832
```bash
2933
npm install --global markdownlint-cli2
3034
./lint.sh
35+
36+
# To fix certain errors directly:
37+
./lint.sh fix
3138
```
3239

3340

34-
## Oxipng
41+
### Oxipng
3542

36-
We use [oxipng](oxipng) to optimize image file size.
43+
We use [oxipng] to optimize image file size.
3744
You can install it with `cargo install oxipng` and then run it as follows:
3845

3946
```bash
@@ -43,8 +50,8 @@ oxipng --strip safe --alpha -r src
4350

4451
## Contributing
4552

46-
This repository is for documentation only. Please open pull requests targeting the gdext library itself in the [main repo][gdext].
47-
Please read the corresponding contributing guidelines in `Contributing.md`.
53+
This repository is for documentation only. For changes in the library itself, please open pull requests and issues in the [main repo][gdext],
54+
and read the [contributing guidelines][gdext-contribute].
4855

4956

5057
## License
@@ -53,10 +60,13 @@ Like gdext itself, the gdext book is licensed under [MPL 2.0][mpl].
5360

5461
[book-web]: https://godot-rust.github.io/book
5562
[gdext]: https://github.com/godot-rust/gdext
63+
[gdext-docs]: https://godot-rust.github.io/docs/gdext/master/godot
64+
[gdext-contribute]: https://github.com/godot-rust/gdext/blob/master/Contributing.md
5665
[gdnative-book]: https://github.com/godot-rust/gdnative-book
5766
[markdownlint]: https://github.com/DavidAnson/markdownlint
5867
[mdbook-admonish]: https://github.com/tommilligan/mdbook-admonish
5968
[mdbook-toc]: https://github.com/badboy/mdbook-toc
6069
[mdBook]: https://github.com/rust-lang-nursery/mdBook
6170
[mpl]: https://www.mozilla.org/en-US/MPL
6271
[oxipng]: https://github.com/shssoichiro/oxipng
72+

src/godot-api/functions.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ The majority of Godot's functionality is exposed via functions inside classes. P
2525

2626
## Godot functions
2727

28-
For methods, the first parameter is the receiver, i.e. the object on which the method is called.
28+
As usual in Rust, functions are split into _methods_ (with a `&self`/`&mut self` receiver) and _associated functions_ (called "static functions"
29+
in Godot).
2930

30-
The Rust API infers the mutability information from the GDExtension API and uses either `&self` or `&mut self` accordingly. Note that this is
31-
**informational** only and bears no safety implications, but it can help you make code more expressive.
31+
To access Godot APIs on a `Gd<T>` pointer, simply call the method on the `Gd` object directly. This works due to `Deref` and `DerefMut` traits,
32+
which give you an object reference through `Gd`. In a [later][book-function-objects] chapter, we'll also see how to call from and into functions
33+
defined in Rust.
3234

3335
```rust
3436
// Call with &self receiver.
@@ -41,6 +43,10 @@ let other: Gd<Node> = ...;
4143
node.add_child(other);
4244
```
4345

46+
Whether a method requires a shared reference (`&T`) or an exclusive one (`&mut T`) depends on how the method is declared in the GDExtension API
47+
(`const` or not). Note that this distinction is **informational** only and bears no safety implications, but it is useful in practice to detect
48+
accidental modification. Technically, you could always just create another pointer via `Gd::clone()`.
49+
4450
Associated functions (called "static" in GDScript) are invoked on the type itself.
4551

4652
```rust
@@ -172,8 +178,9 @@ match result {
172178
[api-acceptdialog-add-button-ex]: https://godot-rust.github.io/docs/gdext/master/godot/classes/struct.AcceptDialog.html#method.add_button_ex
173179
[api-acceptdialog-add-button]: https://godot-rust.github.io/docs/gdext/master/godot/classes/struct.AcceptDialog.html#method.add_button
174180
[api-classes]: https://godot-rust.github.io/docs/gdext/master/godot/classes/index.html
175-
[godot-acceptdialog-add-button]: https://docs.godotengine.org/en/stable/classes/class_acceptdialog.html#class-acceptdialog-method-add-button
176-
[issue-singleton-no-receiver]: https://github.com/godot-rust/gdext/issues/127
177-
[godot-object-call]: https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-call
178181
[api-object-call]: https://godot-rust.github.io/docs/gdext/master/godot/classes/struct.Object.html#method.call
179182
[api-object-trycall]: https://godot-rust.github.io/docs/gdext/master/godot/classes/struct.Object.html#method.try_call
183+
[book-function-objects]: ../register/functions.html#methods-and-object-access
184+
[godot-acceptdialog-add-button]: https://docs.godotengine.org/en/stable/classes/class_acceptdialog.html#class-acceptdialog-method-add-button
185+
[godot-object-call]: https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-call
186+
[issue-singleton-no-receiver]: https://github.com/godot-rust/gdext/issues/127

src/godot-api/objects.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,12 +188,14 @@ generally recommend to fix bugs rather than defensive programming.
188188
## Conclusion
189189

190190
Objects are a central concept in the Rust bindings. They represent instances of Godot classes, both engine- and user-defined.
191-
We have seen how to construct, manage and destroy them. The next chapter will go into calling Godot functions.
191+
We have seen how to construct, manage and destroy them.
192+
193+
But we still have to _use_ objects, i.e. access functionality their class exposes. The next chapter will go into calling Godot functions.
192194

193195

194-
[issue-traits]: https://github.com/godot-rust/gdext/issues/426
195-
[api-gd-from-init-fn]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.from_init_fn
196196
[api-gd-free]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.free
197+
[api-gd-from-init-fn]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.from_init_fn
197198
[api-gd]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html
198199
[api-newalloc]: https://godot-rust.github.io/docs/gdext/master/godot/obj/trait.NewAlloc.html
199200
[api-newgd]: https://godot-rust.github.io/docs/gdext/master/godot/obj/trait.NewGd.html
201+
[issue-traits]: https://github.com/godot-rust/gdext/issues/426

src/register/functions.md

Lines changed: 175 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ So let's implement `to_string()`, here again showing the class definition for qu
8080
#[class(base=Node3D)]
8181
struct Monster {
8282
name: String,
83-
hitpoints: i32
83+
hitpoints: i32,
8484

8585
base: Base<Node3D>,
8686
}
@@ -163,8 +163,171 @@ Of course, it is also possible to declare parameters.
163163

164164
Associated functions are sometimes useful for user-defined constructors, as we will see in the next chapter.
165165

166-
<!-- TODO: base() + base_mut() -->
167-
<!-- TODO: bind() + bind_mut() and their relation to &self/&mut self -->
166+
167+
## Methods and object access
168+
169+
When you define your own Rust functions, there are two use cases that occur very frequently:
170+
171+
- You want to invoke your Rust methods from outside, through a `Gd` pointer.
172+
- You want to access methods of the base class (e.g. `Node3D`).
173+
174+
This section explains how to do both.
175+
176+
177+
### Calling Rust methods (binds)
178+
179+
If you now have a `monster: Gd<Monster>`, which stores a `Monster` object as defined above, you won't be able to simply call
180+
`monster.damage(123)`. Rust is stricter than C++ and requires that only one `&mut Monster` reference exists at any point in time. Since
181+
`Gd` pointers can be freely cloned, direct access through `DerefMut` wouldn't be sufficient to ensure non-aliasing.
182+
183+
To approach this, godot-rust uses the interior mutability pattern, which is quite similar to how [`RefCell`][rust-refcell] works.
184+
185+
In short, whenever you need shared (immutable) access to a Rust object from a `Gd` pointer, use [`Gd::bind()`][api-gd-bind].
186+
Whenever you need exclusive (mutable) access, use [`Gd::bind_mut()`][api-gd-bindmut].
187+
188+
```rust
189+
let monster: Gd<Monster> = ...;
190+
191+
// Immutable access with bind():
192+
let name: GString = monster.bind().get_name();
193+
194+
// Mutable access with bind_mut() -- we rebind the object first:
195+
let mut monster = monster;
196+
monster.bind_mut().damage(123);
197+
```
198+
199+
Regular Rust visibility rules apply: if your function should be visible in another module, declare it as `pub` or `pub(crate)`.
200+
201+
```admonish note title="The need for #[func]"
202+
The `#[func]` attribute _only_ makes a function available to the Godot engine. It is orthogonal to Rust visibility (`pub`, `pub(crate)`, ...)
203+
and does not influence whether a method can be accessed through `Gd::bind()` and `Gd::bind_mut()`.
204+
205+
If you only need to call a function in Rust, do not annotate it with `#[func]`. You can always add this later.
206+
```
207+
208+
`bind()` and `bind_mut()` return _guard objects_. At runtime, the library verifies that the borrow rules are upheld, and panics otherwise.
209+
It can be beneficial to reuse guards across multiple statements, but make sure to keep their scope limited to not unnecessarily constrain access
210+
to objects (especially when using `bind_mut()`).
211+
212+
```rust
213+
fn apply_monster_damage(mut monster: Gd<Monster>, raw_damage: i32) {
214+
// Artificial scope:
215+
{
216+
let guard = monster.bind_mut(); // locks object -->
217+
let armor = guard.get_armor_multiplier();
218+
219+
let damage = (raw_damage as f32 * armor) as i32;
220+
221+
guard.damage(damage)
222+
} // <-- until here, where guard lifetime ends.
223+
224+
// Now you can pass the pointer on to other routines again.
225+
check_if_dead(monster);
226+
}
227+
```
228+
229+
230+
### Base access from `self`
231+
232+
Within a class, you don't directly have a `Gd<T>` pointing to the own instance with base class methods. So you cannot use the approach explained
233+
in the [_Calling functions_ chapter][book-godot-api-functions], where you would simply use `gd.set_position(...)` or similar.
234+
235+
Instead, you can access base class APIs via [`base()` and `base_mut()`][api-withbasefield-base]. This requires that your class defines a
236+
`Base<T>` field. Let's say we add a `velocity` field and two new methods:
237+
238+
```rust
239+
#[derive(GodotClass)]
240+
#[class(base=Node3D)]
241+
struct Monster {
242+
// ...
243+
velocity: Vector2,
244+
base: Base<Node3D>,
245+
}
246+
247+
#[godot_api]
248+
impl Monster {
249+
pub fn apply_movement(&mut self, delta: f32) {
250+
// Read access:
251+
let pos = self.base().get_position();
252+
253+
// Write access (mutating methods):
254+
self.base_mut().set_position(pos + self.velocity * delta)
255+
}
256+
257+
// This method has only read access (&self).
258+
pub fn is_inside_area(&self, rect: Rect2) -> String
259+
{
260+
// We can only call base() here, not base_mut().
261+
let node_name = self.base().get_name();
262+
263+
format!("Monster(name={}, velocity={})", node_name, self.velocity)
264+
}
265+
}
266+
```
267+
268+
Both `base()` and `base_mut()` are defined in an extension trait [`WithBaseField`][api-withbasefield]. They return _guard objects_, which prevent
269+
other access to `self` in line with Rust's borrow rules. You can reuse a guard across multiple statements, but make sure to keep its scope
270+
limited to not unnecessarily constrain access to `self`:
271+
272+
```rust
273+
pub fn apply_movement(&mut self, delta: f32) {
274+
// Artificial scope:
275+
{
276+
let guard = self.base_mut(); // locks `self` -->
277+
let pos = guard.get_position();
278+
279+
guard.set_position(pos + self.velocity * delta)
280+
} // <-- until here, where guard lifetime ends.
281+
282+
// Now can invoke other self methods again.
283+
self.on_position_updated();
284+
}
285+
```
286+
287+
288+
Instead of an extra scope, you can of course also just call [`drop(guard)`][rust-mem-drop].
289+
290+
291+
```admonish note title="Do not combine bind/bind_mut + base/base_mut"
292+
Code like `object.bind().base().some_method()` is unnecessarily verbose and slow.
293+
If you have a `Gd<T>` pointer, use `object.some_method()` directly.
294+
295+
Combining `bind()`/`bind_mut()` immediately with `base()`/`base_mut()`
296+
is a mistake. The latter two should only be called from within the class `impl`.
297+
```
298+
299+
300+
### Obtaining `Gd<Self>` from within
301+
302+
In some cases, you need to get a `Gd<T>` pointer to the current instance. This can occur if you want to pass it to other methods, or if you need
303+
to store a pointer to `self` in a data structure.
304+
305+
`WithBaseField` offers a method `to_gd()`, returning a `Gd<Self>` with the correct type.
306+
307+
Here’s an example. The `monster` is passed a hash map, in which it can register/unregister itself, depending on whether it's alive or not.
308+
309+
```rust
310+
#[godot_api]
311+
impl Monster {
312+
// Function that registers each monster by name, or unregisters it if dead.
313+
fn update_registry(&self, registry: &mut HashMap<String, Gd<Monster>>) {
314+
if self.is_alive() {
315+
let self_as_gd: Gd<Self> = self.to_gd();
316+
registry.insert(self.name.clone(), self_as_gd);
317+
} else {
318+
registry.remove(&self.name);
319+
}
320+
}
321+
}
322+
```
323+
324+
```admonish warning title="Don't bind to_gd() inside class methods"
325+
The methods `base()` and `base_mut()` use a clever mechanism that "re-borrows" the current object reference. This enables re-entrant calls,
326+
such as `self.base().notify(...)`, which may e.g. call `ready(&mut self)`. The `&mut self` here is a reborrow of the call-site `self`.
327+
328+
When you use `to_gd()`, the borrow checker will treat this as an independent object. If you call `bind_mut()` on it, while inside the class impl,
329+
you will immediately get a double-borrow panic. Intead, use `to_gd()` to hand out a pointer and don't access until the current method has ended.
330+
```
168331

169332

170333
## Conclusion
@@ -174,9 +337,18 @@ This page gave you an overview of registering functions with Godot:
174337
- Special methods that hook into the lifecycle of your object.
175338
- User-defined methods and associated functions to expose a Rust API to Godot.
176339

340+
It also showed how methods and objects interact: calling Rust methods through `Gd<T>` and working with base class APIs.
341+
177342
These are just a few use cases, you are very flexible in how you design your interface between Rust and GDScript.
178343
In the next page, we will look into a special kind of functions: constructors.
179344

180345
[api-godot-api]: https://godot-rust.github.io/docs/gdext/master/godot/register/attr.godot_api.html
181346
[api-inode3d]: https://godot-rust.github.io/docs/gdext/master/godot/classes/trait.INode3D.html
182347
[godot-gdscript-functions]: https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#functions
348+
[api-withbasefield]: https://godot-rust.github.io/docs/gdext/master/godot/obj/trait.WithBaseField.html
349+
[api-withbasefield-base]: https://godot-rust.github.io/docs/gdext/master/godot/obj/trait.WithBaseField.html#method.base
350+
[rust-refcell]: https://doc.rust-lang.org/std/cell/struct.RefCell.html
351+
[rust-mem-drop]: https://doc.rust-lang.org/std/mem/fn.drop.html
352+
[book-godot-api-functions]: ../godot-api/functions.html#godot-functions
353+
[api-gd-bind]: https://godot-rust.github.io/docs/gdext/master/godot/prelude/struct.Gd.html#method.bind
354+
[api-gd-bindmut]: https://godot-rust.github.io/docs/gdext/master/godot/prelude/struct.Gd.html#method.bind_mut

0 commit comments

Comments
 (0)