diff --git a/ReadMe.md b/ReadMe.md index 17c6ec3..944ac7e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,10 +1,14 @@ # The godot-rust book The godot-rust book is a user guide for **gdext**, the Rust bindings to Godot 4. -The book is still work-in-progress, and contributions are very welcome. +It covers a large part of the concepts and complements [the API docs][gdext-docs]. +There is also [gdnative-book] for Godot 3. -An online version of the book is available at [godot-rust.github.io/book][book-web]. -For the gdnative book, check out [gdnative-book]. +> [!Tip] +> The book is deployed at **[godot-rust.github.io/book][book-web]**. + + +## Local setup 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: @@ -20,20 +24,23 @@ mdbook serve --open ``` -## Formatting and linting +### Formatting and linting -We use [markdownlint] to enforce a consistent style across the Markdown files. +[markdownlint] enforces a consistent style across the Markdown files. It is automatically run during CI, but if you have the `npm` toolchain, you can also run it locally: ```bash npm install --global markdownlint-cli2 ./lint.sh + +# To fix certain errors directly: +./lint.sh fix ``` -## Oxipng +### Oxipng -We use [oxipng](oxipng) to optimize image file size. +We use [oxipng] to optimize image file size. You can install it with `cargo install oxipng` and then run it as follows: ```bash @@ -43,8 +50,8 @@ oxipng --strip safe --alpha -r src ## Contributing -This repository is for documentation only. Please open pull requests targeting the gdext library itself in the [main repo][gdext]. -Please read the corresponding contributing guidelines in `Contributing.md`. +This repository is for documentation only. For changes in the library itself, please open pull requests and issues in the [main repo][gdext], +and read the [contributing guidelines][gdext-contribute]. ## License @@ -53,6 +60,8 @@ Like gdext itself, the gdext book is licensed under [MPL 2.0][mpl]. [book-web]: https://godot-rust.github.io/book [gdext]: https://github.com/godot-rust/gdext +[gdext-docs]: https://godot-rust.github.io/docs/gdext/master/godot +[gdext-contribute]: https://github.com/godot-rust/gdext/blob/master/Contributing.md [gdnative-book]: https://github.com/godot-rust/gdnative-book [markdownlint]: https://github.com/DavidAnson/markdownlint [mdbook-admonish]: https://github.com/tommilligan/mdbook-admonish @@ -60,3 +69,4 @@ Like gdext itself, the gdext book is licensed under [MPL 2.0][mpl]. [mdBook]: https://github.com/rust-lang-nursery/mdBook [mpl]: https://www.mozilla.org/en-US/MPL [oxipng]: https://github.com/shssoichiro/oxipng + diff --git a/src/godot-api/functions.md b/src/godot-api/functions.md index 968a564..542ed9f 100644 --- a/src/godot-api/functions.md +++ b/src/godot-api/functions.md @@ -25,10 +25,12 @@ The majority of Godot's functionality is exposed via functions inside classes. P ## Godot functions -For methods, the first parameter is the receiver, i.e. the object on which the method is called. +As usual in Rust, functions are split into _methods_ (with a `&self`/`&mut self` receiver) and _associated functions_ (called "static functions" +in Godot). -The Rust API infers the mutability information from the GDExtension API and uses either `&self` or `&mut self` accordingly. Note that this is -**informational** only and bears no safety implications, but it can help you make code more expressive. +To access Godot APIs on a `Gd` pointer, simply call the method on the `Gd` object directly. This works due to `Deref` and `DerefMut` traits, +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 +defined in Rust. ```rust // Call with &self receiver. @@ -41,6 +43,10 @@ let other: Gd = ...; node.add_child(other); ``` +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 +(`const` or not). Note that this distinction is **informational** only and bears no safety implications, but it is useful in practice to detect +accidental modification. Technically, you could always just create another pointer via `Gd::clone()`. + Associated functions (called "static" in GDScript) are invoked on the type itself. ```rust @@ -172,8 +178,9 @@ match result { [api-acceptdialog-add-button-ex]: https://godot-rust.github.io/docs/gdext/master/godot/classes/struct.AcceptDialog.html#method.add_button_ex [api-acceptdialog-add-button]: https://godot-rust.github.io/docs/gdext/master/godot/classes/struct.AcceptDialog.html#method.add_button [api-classes]: https://godot-rust.github.io/docs/gdext/master/godot/classes/index.html -[godot-acceptdialog-add-button]: https://docs.godotengine.org/en/stable/classes/class_acceptdialog.html#class-acceptdialog-method-add-button -[issue-singleton-no-receiver]: https://github.com/godot-rust/gdext/issues/127 -[godot-object-call]: https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-call [api-object-call]: https://godot-rust.github.io/docs/gdext/master/godot/classes/struct.Object.html#method.call [api-object-trycall]: https://godot-rust.github.io/docs/gdext/master/godot/classes/struct.Object.html#method.try_call +[book-function-objects]: ../register/functions.html#methods-and-object-access +[godot-acceptdialog-add-button]: https://docs.godotengine.org/en/stable/classes/class_acceptdialog.html#class-acceptdialog-method-add-button +[godot-object-call]: https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-call +[issue-singleton-no-receiver]: https://github.com/godot-rust/gdext/issues/127 diff --git a/src/godot-api/objects.md b/src/godot-api/objects.md index 513da80..02dc7e4 100644 --- a/src/godot-api/objects.md +++ b/src/godot-api/objects.md @@ -188,12 +188,14 @@ generally recommend to fix bugs rather than defensive programming. ## Conclusion Objects are a central concept in the Rust bindings. They represent instances of Godot classes, both engine- and user-defined. -We have seen how to construct, manage and destroy them. The next chapter will go into calling Godot functions. +We have seen how to construct, manage and destroy them. + +But we still have to _use_ objects, i.e. access functionality their class exposes. The next chapter will go into calling Godot functions. -[issue-traits]: https://github.com/godot-rust/gdext/issues/426 -[api-gd-from-init-fn]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.from_init_fn [api-gd-free]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.free +[api-gd-from-init-fn]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.from_init_fn [api-gd]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html [api-newalloc]: https://godot-rust.github.io/docs/gdext/master/godot/obj/trait.NewAlloc.html [api-newgd]: https://godot-rust.github.io/docs/gdext/master/godot/obj/trait.NewGd.html +[issue-traits]: https://github.com/godot-rust/gdext/issues/426 diff --git a/src/register/functions.md b/src/register/functions.md index 9d6d594..dbfec8b 100644 --- a/src/register/functions.md +++ b/src/register/functions.md @@ -80,7 +80,7 @@ So let's implement `to_string()`, here again showing the class definition for qu #[class(base=Node3D)] struct Monster { name: String, - hitpoints: i32 + hitpoints: i32, base: Base, } @@ -163,8 +163,171 @@ Of course, it is also possible to declare parameters. Associated functions are sometimes useful for user-defined constructors, as we will see in the next chapter. - - + +## Methods and object access + +When you define your own Rust functions, there are two use cases that occur very frequently: + +- You want to invoke your Rust methods from outside, through a `Gd` pointer. +- You want to access methods of the base class (e.g. `Node3D`). + +This section explains how to do both. + + +### Calling Rust methods (binds) + +If you now have a `monster: Gd`, which stores a `Monster` object as defined above, you won't be able to simply call +`monster.damage(123)`. Rust is stricter than C++ and requires that only one `&mut Monster` reference exists at any point in time. Since +`Gd` pointers can be freely cloned, direct access through `DerefMut` wouldn't be sufficient to ensure non-aliasing. + +To approach this, godot-rust uses the interior mutability pattern, which is quite similar to how [`RefCell`][rust-refcell] works. + +In short, whenever you need shared (immutable) access to a Rust object from a `Gd` pointer, use [`Gd::bind()`][api-gd-bind]. +Whenever you need exclusive (mutable) access, use [`Gd::bind_mut()`][api-gd-bindmut]. + +```rust +let monster: Gd = ...; + +// Immutable access with bind(): +let name: GString = monster.bind().get_name(); + +// Mutable access with bind_mut() -- we rebind the object first: +let mut monster = monster; +monster.bind_mut().damage(123); +``` + +Regular Rust visibility rules apply: if your function should be visible in another module, declare it as `pub` or `pub(crate)`. + +```admonish note title="The need for #[func]" +The `#[func]` attribute _only_ makes a function available to the Godot engine. It is orthogonal to Rust visibility (`pub`, `pub(crate)`, ...) +and does not influence whether a method can be accessed through `Gd::bind()` and `Gd::bind_mut()`. + +If you only need to call a function in Rust, do not annotate it with `#[func]`. You can always add this later. +``` + +`bind()` and `bind_mut()` return _guard objects_. At runtime, the library verifies that the borrow rules are upheld, and panics otherwise. +It can be beneficial to reuse guards across multiple statements, but make sure to keep their scope limited to not unnecessarily constrain access +to objects (especially when using `bind_mut()`). + +```rust +fn apply_monster_damage(mut monster: Gd, raw_damage: i32) { + // Artificial scope: + { + let guard = monster.bind_mut(); // locks object --> + let armor = guard.get_armor_multiplier(); + + let damage = (raw_damage as f32 * armor) as i32; + + guard.damage(damage) + } // <-- until here, where guard lifetime ends. + + // Now you can pass the pointer on to other routines again. + check_if_dead(monster); +} +``` + + +### Base access from `self` + +Within a class, you don't directly have a `Gd` pointing to the own instance with base class methods. So you cannot use the approach explained +in the [_Calling functions_ chapter][book-godot-api-functions], where you would simply use `gd.set_position(...)` or similar. + +Instead, you can access base class APIs via [`base()` and `base_mut()`][api-withbasefield-base]. This requires that your class defines a +`Base` field. Let's say we add a `velocity` field and two new methods: + +```rust +#[derive(GodotClass)] +#[class(base=Node3D)] +struct Monster { + // ... + velocity: Vector2, + base: Base, +} + +#[godot_api] +impl Monster { + pub fn apply_movement(&mut self, delta: f32) { + // Read access: + let pos = self.base().get_position(); + + // Write access (mutating methods): + self.base_mut().set_position(pos + self.velocity * delta) + } + + // This method has only read access (&self). + pub fn is_inside_area(&self, rect: Rect2) -> String + { + // We can only call base() here, not base_mut(). + let node_name = self.base().get_name(); + + format!("Monster(name={}, velocity={})", node_name, self.velocity) + } +} +``` + +Both `base()` and `base_mut()` are defined in an extension trait [`WithBaseField`][api-withbasefield]. They return _guard objects_, which prevent +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 +limited to not unnecessarily constrain access to `self`: + +```rust + pub fn apply_movement(&mut self, delta: f32) { + // Artificial scope: + { + let guard = self.base_mut(); // locks `self` --> + let pos = guard.get_position(); + + guard.set_position(pos + self.velocity * delta) + } // <-- until here, where guard lifetime ends. + + // Now can invoke other self methods again. + self.on_position_updated(); + } +``` + + +Instead of an extra scope, you can of course also just call [`drop(guard)`][rust-mem-drop]. + + +```admonish note title="Do not combine bind/bind_mut + base/base_mut" +Code like `object.bind().base().some_method()` is unnecessarily verbose and slow. +If you have a `Gd` pointer, use `object.some_method()` directly. + +Combining `bind()`/`bind_mut()` immediately with `base()`/`base_mut()` +is a mistake. The latter two should only be called from within the class `impl`. +``` + + +### Obtaining `Gd` from within + +In some cases, you need to get a `Gd` pointer to the current instance. This can occur if you want to pass it to other methods, or if you need +to store a pointer to `self` in a data structure. + +`WithBaseField` offers a method `to_gd()`, returning a `Gd` with the correct type. + +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. + +```rust +#[godot_api] +impl Monster { + // Function that registers each monster by name, or unregisters it if dead. + fn update_registry(&self, registry: &mut HashMap>) { + if self.is_alive() { + let self_as_gd: Gd = self.to_gd(); + registry.insert(self.name.clone(), self_as_gd); + } else { + registry.remove(&self.name); + } + } +} +``` + +```admonish warning title="Don't bind to_gd() inside class methods" +The methods `base()` and `base_mut()` use a clever mechanism that "re-borrows" the current object reference. This enables re-entrant calls, +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`. + +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, +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. +``` ## Conclusion @@ -174,9 +337,18 @@ This page gave you an overview of registering functions with Godot: - Special methods that hook into the lifecycle of your object. - User-defined methods and associated functions to expose a Rust API to Godot. +It also showed how methods and objects interact: calling Rust methods through `Gd` and working with base class APIs. + These are just a few use cases, you are very flexible in how you design your interface between Rust and GDScript. In the next page, we will look into a special kind of functions: constructors. [api-godot-api]: https://godot-rust.github.io/docs/gdext/master/godot/register/attr.godot_api.html [api-inode3d]: https://godot-rust.github.io/docs/gdext/master/godot/classes/trait.INode3D.html [godot-gdscript-functions]: https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#functions +[api-withbasefield]: https://godot-rust.github.io/docs/gdext/master/godot/obj/trait.WithBaseField.html +[api-withbasefield-base]: https://godot-rust.github.io/docs/gdext/master/godot/obj/trait.WithBaseField.html#method.base +[rust-refcell]: https://doc.rust-lang.org/std/cell/struct.RefCell.html +[rust-mem-drop]: https://doc.rust-lang.org/std/mem/fn.drop.html +[book-godot-api-functions]: ../godot-api/functions.html#godot-functions +[api-gd-bind]: https://godot-rust.github.io/docs/gdext/master/godot/prelude/struct.Gd.html#method.bind +[api-gd-bindmut]: https://godot-rust.github.io/docs/gdext/master/godot/prelude/struct.Gd.html#method.bind_mut