Skip to content

Commit

Permalink
Merge pull request #41 from godot-rust/feature/godot-api
Browse files Browse the repository at this point in the history
 Godot API tutorial (builtins, objects, functions)
  • Loading branch information
Bromeon authored Apr 16, 2024
2 parents eb5c2c3 + 0b92d70 commit 0af1b5f
Show file tree
Hide file tree
Showing 16 changed files with 644 additions and 149 deletions.
3 changes: 3 additions & 0 deletions book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ additional-css = ["config/mdbook-admonish.css"]

[output.html.redirect]

# Ongoing redirects due to chapter reorganization
"book/intro/objects.html" = "/book/godot-api/objects.html"

# Godot 4: gdext -> one level up
"gdext/index.html" = "/book/index.html"
"gdext/intro/index.html" = "/book/intro/index.html"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
"hr-style": {
"style": "---"
},
// Allows **Header** instead of ## Header -- should not generally be used in the book, but is fine in select cases.
"no-emphasis-as-heading": false,
// Indented vs ```-fenced.
"code-block-style": {
"style": "fenced"
Expand Down
3 changes: 2 additions & 1 deletion lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ show_help() {

# If 'fix' is provided, append '--fix' to the end of the command
if [[ "$1" == "fix" ]]; then
echo ">> Fix mode: apply fixes where possible."
extra="--fix"
elif [[ "$1" == "help" ]] || [[ "$1" == "--help" ]]; then
show_help
Expand All @@ -37,4 +38,4 @@ fi
# do not quote $extra, it shouldn't be an argument if empty.
# do quote glob pattern, shell expands differently than tool itself.
# keep in sync with CI arguments.
markdownlint-cli2 --config .github/other/.markdownlint.jsonc ReadMe.md "src/**/*.md" $extra
markdownlint-cli2 --config config/.markdownlint.jsonc ReadMe.md "src/**/*.md" $extra
5 changes: 4 additions & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
- [Getting Started](intro/index.md)
- [Setup](intro/setup.md)
- [Hello World](intro/hello-world.md)
- [Managing objects in Rust](intro/objects.md)
- [Using the Godot API](godot-api/index.md)
- [Built-in types](godot-api/builtins.md)
- [Objects](godot-api/objects.md)
- [Calling functions](godot-api/functions.md)
- [Registering Rust symbols](register/index.md)
- [Classes](register/classes.md)
- [Functions](register/functions.md)
Expand Down
2 changes: 1 addition & 1 deletion src/contribute/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ We use separators starting with `// ---` to visually divide sections of related
4. Inside files, there is no strict order yet, except `use` and `mod` at the top. Prefer to declare public-facing symbols before private ones.

5. Use flat import statements. If multiple paths have different prefixes, put them on separate lines. Avoid `self`.
```rs
```rust
// Good:
use crate::module;
use crate::module::{Type, function};
Expand Down
201 changes: 201 additions & 0 deletions src/godot-api/builtins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
<!--
~ Copyright (c) godot-rust; Bromeon and contributors.
~ This Source Code Form is subject to the terms of the Mozilla Public
~ License, v. 2.0. If a copy of the MPL was not distributed with this
~ file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->

# Built-in types

The so-called "built-in types" or just "builtins" are the basic types that Godot provides. Notably, these are not _classes_.
See also [basic built-in types in Godot][godot-docs-builtins].


## Table of contents
<!-- toc -->


## List of types

Here is an exhaustive list of all built-in types, by category. We use the GDScript names; below, we explain how they map to Rust.

**Simple types**

- Boolean: `bool`
- Numeric: `int`, `float`

**Composite types**

- Variant (able to hold anything): `Variant`
- String types: `String`, `StringName`, `NodePath`
- Ref-counted containers: `Array` (`Array[T]`), `Dictionary`
- Packed arrays: `Packed*Array` for following element types:
`Byte`, `Int32`, `Int64`, `Float32`, `Float64`, `Vector2`, `Vector3`, `Color`, `String`
- Functional: `Callable`, `Signal`

**Geometric types**

- Vectors: `Vector2`, `Vector2i`, `Vector3`, `Vector3i`, `Vector4`, `Vector4i`
- Bounding boxes: `Rect2`, `Rect2i`, `AABB`
- Matrices: `Transform2D`, `Transform3D`, `Basis`, `Projection`
- Rotation: `Quaternion`
- Geometric objects: `Plane`

**Miscellaneous**

- Color: `Color`
- Resource ID: `RID`


### Rust mapping

Rust types in the gdext API represent the corresponding Godot types in the closest way possible. They are used in parameter and return type
position of API functions, for example.

Most builtins have a 1:1 equivalent (e.g. `Vector2f`, `Color` etc.). The following list highlights some noteworthy mappings:

| GDScript type | Rust type | Rust example expression |
|---------------------------|---------------------------------------|---------------------------|
| `int` | `i64` | `-12345` |
| `float` | `f64` | `3.14159` |
| `real` | `real` (either `f32` or `f64`) | `real!(3.14159)` |
| `String` | `GString` | `"Some string".into()` |
| `StringName` | `StringName` | `"MyClass".into()` |
| `NodePath` | `NodePath` | `Nodes/MyNode".into()` |
| `Array[T]` | `Array<T>` | `array![1, 2, 3]` |
| `Array` | `VariantArray`<br>or `Array<Variant>` | `varray![1, "two", true]` |
| `Dictionary` | `Dictionary` | `dict!{"key": "value"}` |
| `AABB` | `Aabb` | `Aabb::new(pos, size)` |
| `Object` | `Gd<Object>` | `Object::new_alloc()` |
| `SomeClass` | `Gd<SomeClass>` | `Resource::new_gd()` |
| `SomeClass` (nullable) | `Option<Gd<SomeClass>>` | `None` |
| `Variant` (also implicit) | `Variant` | `Variant::nil()` |

Note that Godot does not have nullability information in its class API yet. This means that we have to conservatively assume that objects can
be null, and thus use `Option<Gd<T>>` instead of `Gd<T>` for object return types. This often needs unnecessary unwrapping.

Nullable types are being looked into [on Godot side][godot-nullability-issue]. If there is no upstream solution for a while, we may consider our
own workarounds, but it may come with manual annotation of many APIs.


## String types

Godot provides three string types: `String` ([`GString`][api-gstring] in Rust), [`StringName`][api-stringname], and [`NodePath`][api-nodepath].
`GString` is used as a general-purpose string, while `StringName` is often used for identifiers like class or action names.
The idea is that `StringName` is cheap to construct and compare.[^string-name-Rust]

These types all support `From` traits to convert to/from Rust `String`, and from `&str`. You can thus use `"some_string".into()` expressions.
If you need more explicit typing, use `StringName::from("some_string")`.

`StringName` in particular provides a direct conversion from C-string literals such as `c"string"`, [introduced in Rust 1.77][rust-c-strings].
This can be used for _static_ C-strings, i.e. ones that remain allocated for the entire program lifetime. Don't use them for short-lived ones.


## Arrays and dictionaries

Godot's linear collection type is `Array<T>`. It is generic over its element type `T`, which can be one of the supported Godot types (generally
anything that can be represented by `Variant`). A special type `VariantArray` is provided as an alias for `Array<Variant>`, which is used when
the element type is dynamically typed.

`Dictionary` is a key-value store, where both keys and values are `Variant`. Godot does currently not support generic dictionaries, although
this feature is [under discussion][godot-generic-dicts].

Arrays and dictionaries can be constructed using three macros:

```rust
let a = array![1, 2, 3]; // Array<i64>
let b = varray![1, "two", true]; // Array<Variant>
let c = dict!{"key": "value"}; // Dictionary
```

Their API is similar, but not identical to Rust's standard types `Vec` and `HashMap`. An important difference is that `Array` and `Dictionary`
are reference-counted, which means that `clone()` will not create an independent copy, but another reference to the same instance. Furthermore,
since internal elements are stored as variants, they are not accessible by reference, which is why the `[]` operator (`Index/IndexMut` traits)
is absent.

```rust
let a = array![0, 11, 22];

assert_eq!(a.len(), 3);
assert_eq!(a.get(1), Some(11)); // Note: by value, not Some(&11).
assert_eq!(a.at(1), 11); // Panics on out-of-bounds.

let mut b = a.clone(); // Increment reference-count.
b.set(2, 33); // Modify new ref.
assert_eq!(a.at(2), 33); // Original array has changed.

b.clear();
assert_eq!(b, Array::new()); // new() creates an empty array.
```

```rust
let c = dict! {
"str": "hello",
"int": 42,
"bool": true,
};

assert_eq!(c.len(), 3);
assert_eq!(c.get("str"), Some(&"hello".into()));

let mut d = c.clone(); // Increment reference-count.
d.insert("float", 3.14); // Modify new ref.
assert!(c.contains_key("float")); // Original dict has changed.
```

To iterate, you can use `iter_shared()`. This method works almost like `iter()` on Rust collections, but the name highlights that you do not
have unique access to the collection during iteration, since there might exist another reference. This also means it's your responsibility to
ensure that the collection is not modified in unintended ways during iteration (which should be safe, but may lead to data inconsistencies).

```rust
let a = array!["one", "two", "three"];
let d = dict!{"one": 1, "two": 2.0, "three": Vector3::ZERO};

for elem in a.iter_shared() {
println!("Element: {elem}");
}bu

for (key, value) in d.iter_shared() {
println!("Key: {key}, value: {value}");
}
```


## Packed arrays

`Packed*Array` types are used for storing elements space-efficiently in contiguous memory ("packed"). The `*` stands for the element type, e.g.
`PackedByteArray` or `PackedVector3Array`.

```rust
// Create from slices.
let bytes = PackedByteArray::from(&[0x0A, 0x0B, 0x0C]);
let ints = PackedInt32Array::from(&[1, 2, 3]);

// Access as Rust shared/mutable slices.
let bytes_slice: &[u8] = b.as_slice();
let ints_slice: &mut [i32] = i.as_mut_slice();
```

Unlike `Array`, packed arrays use copy-on-write instead of reference counting. When you clone a packed array, you get a new independent instance.
Cloning is cheap as long as you don't modify either instance. Once you use a write operation (anything with `&mut self`), the packed array will
allocate its own memory and copy the data.

<br>

---

**Footnotes**

[^string-name-Rust]: When constructing `StringName` from `&str` or `String`, the conversion is rather expensive, since it has to go through a
conversion to UTF-32. As Rust recently introduced C-string literals (`c"hello"`), we can now directly construct from them. This is more
efficient, but keeps memory allocated until shutdown, so don't use it for rarely used temporaries.
See [API docs][api-stringname] and [issue #531][issue-stringname-perf] for more information.

[api-gstring]: https://godot-rust.github.io/docs/gdext/master/godot/builtin/struct.GString.html
[api-nodepath]: https://godot-rust.github.io/docs/gdext/master/godot/builtin/struct.NodePath.html
[api-stringname]: https://godot-rust.github.io/docs/gdext/master/godot/builtin/struct.StringName.html
[godot-docs-builtins]: https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#basic-built-in-types
[godot-generic-dicts]: https://github.com/godotengine/godot/pull/78656
[godot-nullability-issue]: https://github.com/godotengine/godot-proposals/issues/162
[issue-stringname-perf]: https://github.com/godot-rust/gdext/issues/531
[rust-c-strings]: https://doc.rust-lang.org/nightly/edition-guide/rust-2021/c-string-literals.html#c-string-literals
Loading

0 comments on commit 0af1b5f

Please sign in to comment.