|
| 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