Skip to content

Commit 1f67221

Browse files
committed
May 2025 dev update
1 parent 383ced8 commit 1f67221

File tree

1 file changed

+177
-0
lines changed

1 file changed

+177
-0
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
+++
2+
# Copyright (c) godot-rust; Bromeon and contributors.
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
title = "May 2025 dev update"
8+
authors = ["Bromeon"]
9+
10+
[extra]
11+
summary = "v0.3: typed signals, async!"
12+
tags = ["dev-update"]
13+
+++
14+
15+
godot-rust is ready for summer, with its fresh **v0.3** release! This update introduces a few major features, sprinkled with a ton of smaller
16+
improvements.
17+
18+
19+
## Typed signals
20+
21+
**Signals** are a core mechanism in Godot's architecture, enabling the Observer pattern for communication between objects.
22+
23+
One problem with signals in GDScript is that they aren't type-checked. Even if you declare them as `signal damage_taken(amount: int)`, this is
24+
only informational -- the number and types of arguments aren't verified by the signal[^gdscript-checks].
25+
26+
Rust, being a language with a focus on safety and robustness, has no excuse to not do this better. So, if you write this...
27+
28+
```rs
29+
#[signal]
30+
fn damage_taken(amount: i32);
31+
```
32+
33+
...you can now do all of this cool stuff:
34+
35+
```rs
36+
// Somewhere in your inherent impl:
37+
fn on_damage_taken(&mut self, amount: i32) {
38+
// ...
39+
}
40+
41+
// Your setup code:
42+
fn ready(&mut self) {
43+
// Connect signal to the method:
44+
self.signals().damage_taken().connect(Self::on_damage_taken);
45+
46+
// Or to an ad-hoc closure:
47+
self.signals().damage_taken().connect(|amount| {
48+
println!("Damage taken: {}", amount);
49+
});
50+
51+
// Or to a method of another object:
52+
let stats: Gd<Stats>;
53+
self.signals().damage_taken().connect_other(&stats, |stats, amount| {
54+
stats.update_total_damage(amount);
55+
});
56+
}
57+
```
58+
59+
Of course, you can also emit the signal in a type-safe way:
60+
61+
```rs
62+
self.signals().damage_taken().emit(42);
63+
```
64+
65+
If you change your `#[signal]` declaration, all the `connect` and `emit` call sites will no longer compile, until you update them.
66+
Fearless refactoring!
67+
68+
For an in-depth tutorial, check out the [Signals chapter in the book][book-signals].
69+
70+
The signal system took several months to evolve and went through many iterations of going back to the drawing board, in
71+
[more than 15 pull requests][changelog-signals]. Special thanks to Houtamelo and Yarwin for going through trait and macro hell to make the
72+
fluent APIs more user-friendly, as well as the many users who gave feedback during the initial design phase. The API will keep evolving!
73+
74+
75+
## Async/await
76+
77+
v0.3 is the first version to introduce the **async/await** paradigm, thanks to the work of TitanNano in [#1043].
78+
79+
Asynchronous programming is provided through signals, which can be _awaited_ in a non-blocking way. This follows the idea of GDScript's own
80+
`await` keyword.
81+
82+
To follow the above example with the `damage_taken` signal, you can now do spawn an async task that waits for the signal to be emitted:
83+
84+
```rs
85+
let player: Gd<Player> = ...;
86+
godot::task::spawn(async move {
87+
godot_print!("Wait for damage to occur...");
88+
89+
// Emitted arguments can be fetched in tuple form.
90+
// If the signal has no parameters, you don't need `let`.
91+
let (dmg,) = player.signals().damage_taken().to_future().await;
92+
93+
godot_print!("Player took {dmg} damage.");
94+
});
95+
```
96+
97+
98+
## `OnEditor` -- fields initialized in the Godot editor
99+
100+
We already have `OnReady<T>` to allow late initialization of fields during `ready()`. The new [`OnEditor<T>`][api-oneditor] struct extends this
101+
idea to fields, which cannot be initialized in neither the `init()` constructor nor a `ready()` method, but instead need to be set externally
102+
in the Godot editor.
103+
104+
In particular, using `Gd<T>` with `#[export]` consistently caused problems with initialization, and is now substituted by `OnEditor<Gd<T>>`.
105+
106+
107+
## Interface traits + final classes
108+
109+
Interface traits like `INode3D`, `IResource` etc. have been ubiquitous in godot-rust. You come across them whenever you implement a constructor
110+
or override virtual functions. Incidentally, we found that 118 of these traits were effectively dead code, because Godot doesn't allow inheriting
111+
their respective classes. Examples include `IFileAccess`, `IIp`, `IScript` and many more. We removed them all, thus moving runtime errors to
112+
compile time.
113+
114+
Additionally, non-inheritable classes in Godot are now properly marked as such in the docs, we currently use the term "final class" for this.
115+
There are now descriptive compile-time errors when attempting `#[class(base = Os)]`.
116+
117+
118+
## Usability
119+
120+
`process()` and `physics_process()` both require a `delta: f64` parameter. Many Godot APIs (sound, timers, etc.) use `f64`, while many others
121+
(vectors, matrices, etc.) use `f32` in default builds. This caused minor but steady friction with extra `as` casts.
122+
Now, you can **additionally** override `process()` and `physics_process()` with `delta: f32`. This is transparently converted from `f64` by the
123+
proc-macro. Details are available in [#1110].
124+
125+
`bind()` and `bind_mut()` have caused lots of runtime borrow errors. Fear not: they will keep doing exactly that. However, with
126+
`RUST_BACKTRACE=1`, you can now retrieve the exact call stacks where the problem occurs. Not just once it panics, but retroactively where
127+
the previous borrow originated! Check out [#1094].
128+
129+
String types (`GString`, `StringName`, `NodePath`) now support loading from `&[u8]` and `&CStr`, with specified text encoding.
130+
This bridges the gap to low-level GDExtension APIs, with additional validation on top.
131+
132+
When loading resources into fields, there is now `#[init(load = "PATH")]`, which calls `OnReady::from_loaded()`, which again calls `load()` and
133+
stores the result in the `OnReady`.
134+
135+
Generated docs for the editor now support `@experimental` and `@deprecated` attributes, and will be displayed as such in the Godot editor.
136+
137+
138+
## Project structure improvements
139+
140+
Examples were moved into a separate repo [demo-projects], with their own issue management and continuous integration.
141+
142+
API docs now mention (again) if a feature is only available from a certain Godot version, or if it is gated behind `experimental-godot-api`.
143+
144+
In the library, we got rid of two dependencies and reduced the "minimum codegen" set, which reduces compile times, especially in CI.
145+
Lots of smaller refactorings have happened to keep the development process as smooth as possible.
146+
147+
148+
## That's it for now!
149+
150+
We hope you enjoy the improvements since the [2024 review][dev-update-2024], and wish you great success in your projects!
151+
Whether those are games, plugins or other software, join our [Discord][discord] and let us know how you use the library in `#showcase`!
152+
153+
As always, the complete list of changes is available in the [changelog][changelog]. See also recently merged [pull requests][pull-requests].
154+
155+
156+
[#650]: https://github.com/godot-rust/gdext/pull/650
157+
[#1043]: https://github.com/godot-rust/gdext/pull/1043
158+
[#1094]: https://github.com/godot-rust/gdext/pull/1094
159+
[#1110]: https://github.com/godot-rust/gdext/pull/1110
160+
[api-oneditor]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.OnEditor.html
161+
[book-signals]: https://godot-rust.github.io/book/register/signals.html
162+
[changelog]: https://github.com/godot-rust/gdext/blob/master/Changelog.md
163+
[demo-projects]: https://github.com/godot-rust/demo-projects/
164+
[dev-update-2024]: ../godot-rust-2024-review
165+
[discord]: https://discord.gg/aKUCJ8rJsc
166+
[issues]: https://github.com/godot-rust/gdext/issues
167+
[pull-requests]: https://github.com/godot-rust/gdext/pulls?q=is%3Apr+is%3Amerged
168+
169+
<br><br>
170+
171+
---
172+
173+
### Footnotes
174+
175+
[^gdscript-checks]: Arguments _may_ be checked by the receiving function (connected to the signal), depending on its signature, and only
176+
at runtime. However, the `signal` itself doesn't verify this.
177+
See also the last _🛈 Note_ box [here](https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html#custom-signals).

0 commit comments

Comments
 (0)