June 2024 dev update
We'll start our summer with another development update on godot-rust.
Crates.io release
Let's begin with the biggest one: we released on crates.io!
Aptly named godot
, you can immediately start using the crate:
[dependencies]
godot = "0.1"
This may come a bit late for some, however there weren't many benefits for crate releases in the early stages, whereas Cargo has integrated well with Git repos. In 2023, the library quickly progressed, and a more relaxed versioning approach enabled fast prototyping. Experimentation still happens, but things are a bit more stable now.
Unlike typical Rust projects, our dependency setup is relatively complex: besides Rust crate version, we also account for an orthogonal Godot version. It is a declared goal of godot-rust to provide compatibility with multiple Godot versions at the same time. For this, we have converged towards a very flexible approach (see next section), however this needed a lot of design work, as well as a great deal of automation to cover 13+ individual Godot stable releases.
Selecting an API level
The API level specifies the minimum version of GDExtension (and the Godot class API) with which gdext is compatible. This is especially important for plugin writers who want to support multiple Godot versions; more details in the book.
The API level previously required a [patch]
statement on a top-level Cargo.toml
to bend dependencies to comply. Now, this is much simpler, just set the api-*
feature when adding the godot-rust dependency. By default, a recent stable minor release is used (currently 4.2).
[dependencies]
godot = { version = "0.1", features = ["api-4.1"] } # Compatible with 4.1+
Streamlined modules
The old module structure grew organically as we mapped more and more Godot APIs, thus a lot of new symbols were just added where it was convenient to develop, rather than where they made sense from a user perspective. A good example was the godot::engine
god module which contained everything from Godot classes/enums/utility functions over GFile
and ScriptInstance
high-level APIs, to translation macros.
The most important modules are now:
- Godot API:
builtin
types,classes
,global
enums/utilities/print - Extra functionality on top of Godot:
tools
- Object-related:
obj
, andobj::script
for script instances - Registration of Rust symbols:
register
- Entry point and type meta-information:
init
andmeta
The refactoring details are listed in #729, #732, #733, #735, and #738.
Script instance API
Script instances are a relatively unknown but very powerful GDExtension feature that allows custom implementations of scripts, in our case Rust-based ones. This means you can attach a Rust script to scene nodes, like you would do with GDScript or C# scripts.
TitanNano has done groundbreaking work on that front, improving UX by enabling re-entrancy, fixing property/method accessors or properly handling size changes. His improvements have led to ScriptInstance
implementations behaving more and more like GodotClass
ones, thus feeling very natural within gdext.
Multithreading fixes
Situations where Godot accesses Rust code from different threads (e.g. in callbacks) has sometimes caused panics, because access went through naive dynamic borrow-checks (à la RefCell
). Thanks to TitanNano and lilizoey, these obstacles have been removed with proper synchronization.
Collection accessors
Arrays and dictionaries received better accessors, for example an at(index)
function. Even untyped Dictionaries
are quite ergonomic to use, if you know the element types:
// Create empty dictionary and add key-value pairs.
let dict = dict! {
"key": "some value",
"key2": 345,
"key2": Vector2i::new(1, 2),
};
// Access elements, typed and untyped.
let value: Variant = dict.at("key");
let value: GString = dict.at("key").to(); // Variant::to() extracts GString.
let maybe: Option<Variant> = dict.get("absent_key"); // None.
// Iterate as (Variant, Variant).
for (key, value) in dict.iter_shared() { ... }
// Key type is known -- iterate over (String, Variant).
for (key, value) in dict.iter_shared().typed::<String, Variant>() { ... }
On the packed side, Packed*Array
types now have an API which is more consistent with Array
. In PR #725, I added the Index
/IndexMut
traits which allow -- unlike Array
/Dictionary
-- direct element referencing via array[index]
syntax.
Geometric API consistency
The user joriskleiber has recently contributed two important refactorings to the geometric types in godot::builtin
. On one hand, more of the vector API was moved to macros, which ensures that function signatures and implementation are identical for the 6 vector types (Vector2
, Vector2i
, Vector3
, Vector3i
, Vector4
, Vector4i
).
On the other, there was quite an inconsistent handling of by-ref and by-value parameters, most notably self
vs &self
in operations that don't modify the instance. Joris addressed this in #751, so that all geometric primities in one "group" (e.g. vector, matrix, bounding box) now have the same signatures. Furthermore, there is an ongoing PR to add functions such as normalized_or_zero()
to vectors.
Error messages
Rust 1.78 added #[diagnostic::on_unimplemented]
, a feature that allows customizing trait error messages. We immediately implemented this in #692, as it benefits user-facing error messages in many places.
As an example, the following class should have either #[class(init)]
or a manual init
function, or alternatively #[class(no_init)]
to disable the constructor.
#[derive(GodotClass)]
struct MyClass {}
Previously, the error message was:
error[E0277]: the trait bound `MyClass: GodotDefault` is not satisfied
--> path/to/file.rs:123:4
|
123 | struct MyClass {}
| ^^^^^^^ the trait `GodotDefault` is not implemented for `MyClass`
|
After the change:
error[E0277]: Class `MyClass` requires either an `init` constructor, or
explicit opt-out
--> path/to/file.rs:123:4
|
123 | struct MyClass {}
| ^^^^^^^ needs `init`
|
= help: the trait `GodotDefault` is not implemented for `MyClass`
= note: To provide a default constructor, use `#[class(init)]` or implement
an `init` method
= note: To opt out, use `#[class(no_init)]`
= note: see also: https://godot-rust.github.io/book/register/constructors.html
There are now very concrete notes to guide the user, and even a link for further reading.
More to come
The complete list of merged pull request is available on GitHub.
Now that a lot of the tooling aspects around godot-rust publishing are taken care of, we can focus even more on features. If you are interested in the development, take a look at the issue tracker and voice your opinion on GitHub or Discord!