Skip to content

Commit

Permalink
Merge #167
Browse files Browse the repository at this point in the history
167: Call error messages, `gdext` rename r=Bromeon a=Bromeon

Improves the diagnostics when calling into Godot APIs.

Also updates documentation to reflect the rename to `gdext` (from whatever the name was before 😀).

Makes `Debug` impl for `GodotString`, `StringName` and `NodePath` more consistent:
* `"string"`
* `&"name"`
* `^"path"`

Co-authored-by: Jan Haller <bromeon@gmail.com>
  • Loading branch information
bors[bot] and Bromeon authored Mar 10, 2023
2 parents 9353407 + 207c4e7 commit 54cb201
Show file tree
Hide file tree
Showing 19 changed files with 202 additions and 120 deletions.
10 changes: 5 additions & 5 deletions Contributing.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Contributing to `gdextension`
# Contributing to `gdext`

At this stage, we appreciate if users experiment with the library, use it in small projects and report issues and bugs they encounter.

If you plan to make bigger contributions, make sure to discuss them in a [GitHub issue] first. Since the library is evolving quickly, this avoids that multiple people work on the same thing or implement features in a way that doesn't work with other parts. Also don't hesitate to talk to the developers in the `#dev-gdextension` channel on [Discord]!
If you plan to make bigger contributions, make sure to discuss them in a [GitHub issue] first. Since the library is evolving quickly, this avoids that multiple people work on the same thing or implement features in a way that doesn't work with other parts. Also don't hesitate to talk to the developers in the `#contrib-gdext` channel on [Discord]!

## Check script

Expand All @@ -22,7 +22,7 @@ $ ln -sf check.sh .git/hooks/pre-commit

## Unit tests

Because most of `gdextension` interacts with the Godot engine, which is not available from the test executable, unit tests (using `cargo test` and the `#[test]` attribute) are pretty limited in scope.
Because most of `gdext` interacts with the Godot engine, which is not available from the test executable, unit tests (using `cargo test` and the `#[test]` attribute) are pretty limited in scope.

Because additional flags might be needed, the preferred way to run unit tests is through the `check.sh` script:

Expand All @@ -32,7 +32,7 @@ $ ./check.sh test

## Integration tests

The `itest/` directory contains a suite of integration tests that actually exercise `gdextension` from within Godot.
The `itest/` directory contains a suite of integration tests that actually exercise `gdext` from within Godot.

The `itest/rust` directory is a Rust `cdylib` library project that can be loaded as a GDExtension in Godot, with an entry point for running integration tests. The `itest/godot` directory contains the Godot project that loads this library and invokes the test suite.

Expand Down Expand Up @@ -71,5 +71,5 @@ To run the testing suite with `double-precision` enabled you may add `--double`
$ check.sh --double
```

[GitHub issue]: https://github.com/godot-rust/gdextension/issues
[GitHub issue]: https://github.com/godot-rust/gdext/issues
[Discord]: https://discord.gg/aKUCJ8rJsc
44 changes: 26 additions & 18 deletions ReadMe.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
![logo.png](assets/gdextension-ferris.png)
![logo.png](assets/gdext-ferris.png)

# Rust bindings for GDExtension

This is an early-stage library to bind the **Rust** language to **Godot 4**.
_[Discord] | [Mastodon] | [Twitter]_

**gdext** is an early-stage library to bind the **Rust** language to **Godot 4**.

[Godot] is an open-source game engine, whose upcoming version 4.0 brings several improvements.
Its _GDExtension_ API allows integrating third-party languages and libraries.
Expand All @@ -17,10 +19,10 @@ Its _GDExtension_ API allows integrating third-party languages and libraries.
> * No stability guarantees. APIs will break frequently (for releases, we try to take SemVer seriously though).
> Resolving the above two points has currently more weight than a stable API.
We do not recommend building a larger project in GDExtension-Rust yet.
We do not recommend building a larger project in gdext yet.
However, the library can serve as a playground for experimenting.

To get an overview of currently supported features, consult [#24](https://github.com/godot-rust/gdextension/issues/24).
To get an overview of currently supported features, consult [#24](https://github.com/godot-rust/gdext/issues/24).
At this point, there is **no** support for Android, iOS or WASM. Contributions are very welcome!


Expand All @@ -40,7 +42,7 @@ In your Cargo.toml, add:

```toml
[dependencies]
godot = { git = "https://github.com/godot-rust/gdextension", branch = "master" }
godot = { git = "https://github.com/godot-rust/gdext", branch = "master" }

[lib]
crate-type = ["cdylib"]
Expand All @@ -53,21 +55,25 @@ To register the GDExtension library with Godot, you need to create two files rel

The `[configuration]` section should be copied as-is.
The `[libraries]` section should be updated to match the paths of your dynamic Rust libraries.
```ini
[configuration]
entry_symbol = "gdextension_rust_init"

[libraries]
linux.64 = "res://../rust/target/debug/lib{my_ext}.so"
windows.64 = "res://../rust/target/debug/{my_ext}.dll"
macos.64 = "res://../rust/target/debug/{my_ext}.dylib"
```
```ini
[configuration]
entry_symbol = "gdext_rust_init"

[libraries]
linux.debug.x86_64 = "res://../rust/target/debug/lib{my_ext}.so"
linux.release.x86_64 = "res://../rust/target/release/lib{my_ext}.so"
windows.debug.x86_64 = "res://../rust/target/debug/{my_ext}.dll"
windows.release.x86_64 = "res://../rust/target/release/{my_ext}.dll"
macos.debug = "res://../rust/target/debug/{my_ext}.dylib"
macos.release = "res://../rust/target/release/{my_ext}.dylib"
```
(Note that for exporting your project, you'll need to use paths inside `res://`).

2. A second file `res://.godot/extension_list.cfg` should be generated once you open the Godot editor for the first time.
If not, you can also manually create it, simply containing the Godot path to your `.gdextension` file:
```
res://MyExt.gdextension
```
```
res://MyExt.gdextension
```

### Examples

Expand All @@ -76,7 +82,7 @@ This integrates a small game with Godot and has all the necessary steps set up.

API documentation can be generated locally using `cargo doc -p godot --no-deps --open`.

If you need help, join our [Discord] server and ask in the `#help-gdextension` channel!
If you need help, join our [Discord] server and ask in the `#help-gdext` channel!


## License
Expand All @@ -96,3 +102,5 @@ Contributions are very welcome! If you want to help out, see [`Contributing.md`]
[`gdnative`]: https://github.com/godot-rust/gdnative
[mpl]: https://www.mozilla.org/en-US/MPL/
[Discord]: https://discord.gg/aKUCJ8rJsc
[Mastodon]: https://mastodon.gamedev.place/@GodotRust
[Twitter]: https://twitter.com/GodotRust
2 changes: 1 addition & 1 deletion assets/asset-licenses.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Asset licenses

The godot-rust logos for both GDNative and GDExtension are derived from the following work,
The godot-rust logos for both _gdnative_ and _gdext_ are derived from the following work,
with changes applied from members of the godot-rust community.

| Asset | Website | Author | License |
Expand Down
File renamed without changes
15 changes: 7 additions & 8 deletions check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
# Small utility to run tests locally
# Similar to minimal-ci

# Note: at the moment, there is a lot of useless recompilation.
# This should be better once unit tests and #[cfg] are sorted out.
# Note: at the moment, there is some useless recompilation, which could be improved.

# --help menu
for arg in $@; do
Expand Down Expand Up @@ -123,13 +122,13 @@ END='\033[0m'
for cmd in "${cmds[@]}"; do
echo "> $cmd"
$cmd || {
printf "$RED\n=========================="
printf "\ngodot-rust checker FAILED."
printf "\n==========================\n$END"
printf "$RED\n====================="
printf "\ngdext: checks FAILED."
printf "\n=====================\n$END"
exit 1
}
done

printf "$GREEN\n=============================="
printf "\ngodot-rust checker SUCCESSFUL."
printf "\n==============================\n$END"
printf "$GREEN\n========================="
printf "\ngdext: checks SUCCESSFUL."
printf "\n=========================\n$END"
2 changes: 1 addition & 1 deletion examples/dodge-the-creeps/godot/DodgeTheCreeps.gdextension
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[configuration]
entry_symbol = "gdextension_rust_init"
entry_symbol = "gdext_rust_init"

[libraries]
linux.64 = "res://../../../target/debug/libdodge_the_creeps.so"
Expand Down
79 changes: 59 additions & 20 deletions godot-codegen/src/class_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//! Generates a file for each Godot engine + builtin class
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use quote::{format_ident, quote, ToTokens};
use std::path::{Path, PathBuf};

use crate::api_parser::*;
Expand Down Expand Up @@ -712,12 +712,43 @@ fn make_function_definition(

let is_varcall = variant_ffi.is_some();
let fn_name = safe_ident(function_name);
let (params, arg_exprs) = make_params(method_args, is_varcall, ctx);
let [params, variant_types, arg_exprs, arg_names] = make_params(method_args, is_varcall, ctx);

let (prepare_arg_types, error_fn_context);
if variant_ffi.is_some() {
// varcall (using varargs)
prepare_arg_types = quote! {
let mut __arg_types = Vec::with_capacity(__explicit_args.len() + varargs.len());
// __arg_types.extend(__explicit_args.iter().map(Variant::get_type));
__arg_types.extend(varargs.iter().map(Variant::get_type));
let __vararg_str = varargs.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(", ");
};

let joined = arg_names
.iter()
.map(|n| format!("{{{n}:?}}"))
.collect::<Vec<_>>()
.join(", ");

let fmt = format!("{function_name}({joined}; {{__vararg_str}})");
error_fn_context = quote! { &format!(#fmt) };
} else {
// ptrcall
prepare_arg_types = quote! {
let __arg_types = [
#( #variant_types ),*
];
};
error_fn_context = function_name.to_token_stream();
};

let (return_decl, call_code) = make_return(
return_value,
variant_ffi.as_ref(),
varcall_invocation,
ptrcall_invocation,
prepare_arg_types,
error_fn_context,
ctx,
);

Expand All @@ -733,7 +764,7 @@ fn make_function_definition(
#( #arg_exprs ),*
];

let mut __args = Vec::new();
let mut __args = Vec::with_capacity(__explicit_args.len() + varargs.len());
__args.extend(__explicit_args.iter().map(Variant::#sys_method));
__args.extend(varargs.iter().map(Variant::#sys_method));

Expand Down Expand Up @@ -789,39 +820,41 @@ fn make_params(
method_args: &Option<Vec<MethodArg>>,
is_varcall: bool,
ctx: &mut Context,
) -> (Vec<TokenStream>, Vec<TokenStream>) {
) -> [Vec<TokenStream>; 4] {
let empty = vec![];
let method_args = method_args.as_ref().unwrap_or(&empty);

let mut params = vec![];
let mut variant_types = vec![];
let mut arg_exprs = vec![];
let mut arg_names = vec![];
for arg in method_args.iter() {
let param_name = safe_ident(&arg.name);
let param_ty = to_rust_type(&arg.type_, ctx);

params.push(quote! { #param_name: #param_ty });
if is_varcall {
arg_exprs.push(quote! {
<#param_ty as ToVariant>::to_variant(&#param_name)
});
} else if let RustTy::EngineClass { tokens: path, .. } = param_ty {
arg_exprs.push(quote! {
<#path as AsArg>::as_arg_ptr(&#param_name)
});
let arg_expr = if is_varcall {
quote! { <#param_ty as ToVariant>::to_variant(&#param_name) }
} else if let RustTy::EngineClass { tokens: path, .. } = &param_ty {
quote! { <#path as AsArg>::as_arg_ptr(&#param_name) }
} else {
arg_exprs.push(quote! {
<#param_ty as sys::GodotFfi>::sys_const(&#param_name)
});
}
quote! { <#param_ty as sys::GodotFfi>::sys_const(&#param_name) }
};

params.push(quote! { #param_name: #param_ty });
variant_types.push(quote! { <#param_ty as VariantMetadata>::variant_type() });
arg_exprs.push(arg_expr);
arg_names.push(quote! { #param_name });
}
(params, arg_exprs)
[params, variant_types, arg_exprs, arg_names]
}

fn make_return(
return_value: Option<&MethodReturn>,
variant_ffi: Option<&VariantFfi>,
varcall_invocation: &TokenStream,
ptrcall_invocation: &TokenStream,
prepare_arg_types: TokenStream,
error_fn_context: TokenStream, // only for panic message
ctx: &mut Context,
) -> (TokenStream, TokenStream) {
let return_decl: TokenStream;
Expand Down Expand Up @@ -851,7 +884,10 @@ fn make_return(
let variant = Variant::#from_sys_init_method(|return_ptr| {
let mut __err = sys::default_call_error();
#varcall_invocation
sys::panic_on_call_error(&__err);
if __err.error != sys::GDEXTENSION_CALL_OK {
#prepare_arg_types
sys::panic_call_error(&__err, #error_fn_context, &__arg_types);
}
});
#return_expr
}
Expand All @@ -863,7 +899,10 @@ fn make_return(
let mut __err = sys::default_call_error();
let return_ptr = std::ptr::null_mut();
#varcall_invocation
sys::panic_on_call_error(&__err);
if __err.error != sys::GDEXTENSION_CALL_OK {
#prepare_arg_types
sys::panic_call_error(&__err, #error_fn_context, &__arg_types);
}
}
}
(None, Some(RustTy::EngineClass { tokens, .. })) => {
Expand Down
17 changes: 13 additions & 4 deletions godot-core/src/builtin/node_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use std::fmt;

use crate::builtin::GodotString;
use godot_ffi as sys;
use godot_ffi::{ffi_methods, GDExtensionTypePtr, GodotFfi};
use std::fmt::{Display, Formatter, Result as FmtResult};

pub struct NodePath {
opaque: sys::types::OpaqueNodePath,
Expand Down Expand Up @@ -59,10 +60,18 @@ impl From<&str> for NodePath {
}
}

impl Display for NodePath {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
impl fmt::Display for NodePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = GodotString::from(self);
<GodotString as fmt::Display>::fmt(&string, f)
}
}

/// Uses literal syntax from GDScript: `^"node_path"`
impl fmt::Debug for NodePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = GodotString::from(self);
<GodotString as Display>::fmt(&string, f)
write!(f, "^\"{string}\"")
}
}

Expand Down
3 changes: 2 additions & 1 deletion godot-core/src/builtin/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,11 @@ impl fmt::Display for GodotString {
}
}

/// Uses literal syntax from GDScript: `"string"`
impl fmt::Debug for GodotString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = String::from(self);
write!(f, "GodotString(\"{s}\")")
write!(f, "\"{s}\"")
}
}

Expand Down
21 changes: 9 additions & 12 deletions godot-core/src/builtin/string_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::builtin::GodotString;
use godot_ffi as sys;
use sys::{ffi_methods, GodotFfi};

use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::fmt;
use std::hash::{Hash, Hasher};

#[repr(C)]
Expand Down Expand Up @@ -68,21 +68,18 @@ impl Default for StringName {
}
}

impl Display for StringName {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
// TODO consider using GDScript built-in to_string()

impl fmt::Display for StringName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = GodotString::from(self);
<GodotString as Display>::fmt(&s, f)
<GodotString as fmt::Display>::fmt(&s, f)
}
}

impl Debug for StringName {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
// TODO consider using GDScript built-in to_string()

let s = GodotString::from(self);
<GodotString as Debug>::fmt(&s, f)
/// Uses literal syntax from GDScript: `&"string_name"`
impl fmt::Debug for StringName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = GodotString::from(self);
write!(f, "&\"{string}\"")
}
}

Expand Down
Loading

0 comments on commit 54cb201

Please sign in to comment.