Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AsObjectArg trait enabling implicit conversions for object parameters #800

Merged
merged 6 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/dodge-the-creeps/rust/src/main_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl Main {

mob_scene.set_rotation(direction);

self.base_mut().add_child(mob_scene.clone().upcast());
self.base_mut().add_child(&mob_scene);

let mut mob = mob_scene.cast::<mob::Mob>();
let range = {
Expand Down
6 changes: 5 additions & 1 deletion godot-codegen/src/conv/type_conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,12 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
RustTy::BuiltinIdent(rustify_ty(ty))
} else {
let ty = rustify_ty(ty);
let qualified_class = quote! { crate::classes::#ty };

RustTy::EngineClass {
tokens: quote! { Gd<crate::classes::#ty> },
tokens: quote! { Gd<#qualified_class> },
arg_view: quote! { ObjectArg<#qualified_class> },
impl_as_arg: quote! { impl AsObjectArg<#qualified_class> },
inner_class: ty,
}
}
Expand Down
22 changes: 15 additions & 7 deletions godot-codegen/src/generator/default_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,13 @@ pub fn make_function_definition_with_defaults(

let receiver_param = &code.receiver.param;
let receiver_self = &code.receiver.self_prefix;
let (required_params, required_args) =
functions_common::make_params_and_args(&required_fn_params);

let [required_params_impl_asarg, _, required_args_asarg] =
functions_common::make_params_exprs(required_fn_params.iter().cloned(), false, true, true);

let [required_params_plain, _, required_args_internal] =
functions_common::make_params_exprs(required_fn_params.into_iter(), false, false, false);

let return_decl = &sig.return_value().decl;

// Technically, the builder would not need a lifetime -- it could just maintain an `object_ptr` copy.
Expand All @@ -73,7 +78,7 @@ pub fn make_function_definition_with_defaults(
impl #builder_lifetime #builder_ty #builder_lifetime {
fn new(
#object_param
#( #required_params, )*
#( #required_params_plain, )*
) -> Self {
Self {
#( #builder_inits, )*
Expand All @@ -95,21 +100,21 @@ pub fn make_function_definition_with_defaults(
#[inline]
#vis fn #simple_fn_name(
#receiver_param
#( #required_params, )*
#( #required_params_impl_asarg, )*
) #return_decl {
#receiver_self #extended_fn_name(
#( #required_args, )*
#( #required_args_internal, )*
).done()
}

#[inline]
#vis fn #extended_fn_name(
#receiver_param
#( #required_params, )*
#( #required_params_impl_asarg, )*
) -> #builder_ty #builder_anon_lifetime {
#builder_ty::new(
#object_arg
#( #required_args, )*
#( #required_args_asarg, )*
)
}
};
Expand Down Expand Up @@ -242,6 +247,8 @@ fn make_extender(
default_value,
} = param;

let type_ = type_.param_decl();

// Initialize with default parameters where available, forward constructor args otherwise
let init = if let Some(value) = default_value {
quote! { #name: #value }
Expand All @@ -256,6 +263,7 @@ fn make_extender(

for param in default_fn_params {
let FnParam { name, type_, .. } = param;
let type_ = type_.param_decl();

let method = quote! {
#[inline]
Expand Down
101 changes: 67 additions & 34 deletions godot-codegen/src/generator/functions_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,24 +118,30 @@ pub fn make_function_definition(
(TokenStream::new(), TokenStream::new())
};

let [params, param_types, arg_names] = make_params_exprs(sig.params());
let [params, param_types, arg_names] = make_params_exprs(
sig.params().iter(),
sig.is_virtual(),
!has_default_params, // For *_full function, we don't need impl AsObjectArg<T> parameters
!has_default_params, // or arg.as_object_arg() calls.
);

let rust_function_name_str = sig.name();
let primary_fn_name = if has_default_params {
format_ident!("{}_full", safe_ident(rust_function_name_str))
} else {
safe_ident(rust_function_name_str)
};

let (default_fn_code, default_structs_code) = if has_default_params {
default_parameters::make_function_definition_with_defaults(
sig,
code,
&primary_fn_name,
cfg_attributes,
)
let (primary_fn_name, default_fn_code, default_structs_code);
if has_default_params {
primary_fn_name = format_ident!("{}_full", safe_ident(rust_function_name_str));

(default_fn_code, default_structs_code) =
default_parameters::make_function_definition_with_defaults(
sig,
code,
&primary_fn_name,
cfg_attributes,
);
} else {
(TokenStream::new(), TokenStream::new())
primary_fn_name = safe_ident(rust_function_name_str);
default_fn_code = TokenStream::new();
default_structs_code = TokenStream::new();
};

let return_ty = &sig.return_value().type_tokens();
Expand Down Expand Up @@ -189,6 +195,14 @@ pub fn make_function_definition(
// Note: all varargs functions are non-static, which is why there are some shortcuts in try_*() argument forwarding.
// This can be made more complex if ever necessary.

// A function() may call try_function(), its arguments should not have .as_object_arg().
let [_, _, arg_names_without_asarg] = make_params_exprs(
sig.params().iter(),
false,
!has_default_params, // For *_full function, we don't need impl AsObjectArg<T> parameters
false, // or arg.as_object_arg() calls.
);

quote! {
/// # Panics
/// This is a _varcall_ method, meaning parameters and return values are passed as `Variant`.
Expand All @@ -199,7 +213,7 @@ pub fn make_function_definition(
#( #params, )*
varargs: &[Variant]
) #return_decl {
Self::#try_fn_name(self, #( #arg_names, )* varargs)
Self::#try_fn_name(self, #( #arg_names_without_asarg, )* varargs)
.unwrap_or_else(|e| panic!("{e}"))
}

Expand Down Expand Up @@ -278,19 +292,6 @@ pub fn make_receiver(qualifier: FnQualifier, ffi_arg_in: TokenStream) -> FnRecei
self_prefix,
}
}

pub fn make_params_and_args(method_args: &[&FnParam]) -> (Vec<TokenStream>, Vec<TokenStream>) {
method_args
.iter()
.map(|param| {
let param_name = &param.name;
let param_ty = &param.type_;

(quote! { #param_name: #param_ty }, quote! { #param_name })
})
.unzip()
}

pub fn make_vis(is_private: bool) -> TokenStream {
if is_private {
quote! { pub(crate) }
Expand All @@ -302,18 +303,50 @@ pub fn make_vis(is_private: bool) -> TokenStream {
// ----------------------------------------------------------------------------------------------------------------------------------------------
// Implementation

fn make_params_exprs(method_args: &[FnParam]) -> [Vec<TokenStream>; 3] {
pub(crate) fn make_params_exprs<'a>(
method_args: impl Iterator<Item = &'a FnParam>,
is_virtual: bool,
param_is_impl_asarg: bool,
arg_is_asarg: bool,
) -> [Vec<TokenStream>; 3] {
let mut params = vec![];
let mut param_types = vec![];
let mut param_types = vec![]; // or non-generic params
let mut arg_names = vec![];

for param in method_args.iter() {
for param in method_args {
let param_name = &param.name;
let param_ty = &param.type_;

params.push(quote! { #param_name: #param_ty });
param_types.push(quote! { #param_ty });
arg_names.push(quote! { #param_name });
// Objects (Gd<T>) use implicit conversions via AsObjectArg. Only use in non-virtual functions.
match &param.type_ {
RustTy::EngineClass {
arg_view,
impl_as_arg,
..
} if !is_virtual => {
// Parameter declarations in signature: impl AsObjectArg<T>
if param_is_impl_asarg {
params.push(quote! { #param_name: #impl_as_arg });
} else {
params.push(quote! { #param_name: #arg_view });
}

// Argument names in function body: arg.as_object_arg() vs. arg
if arg_is_asarg {
arg_names.push(quote! { #param_name.as_object_arg() });
} else {
arg_names.push(quote! { #param_name });
}

param_types.push(quote! { #arg_view });
}

_ => {
params.push(quote! { #param_name: #param_ty });
arg_names.push(quote! { #param_name });
param_types.push(quote! { #param_ty });
}
}
}

[params, param_types, arg_names]
Expand Down
19 changes: 18 additions & 1 deletion godot-codegen/src/models/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ impl FnParam {
}
}

/// `impl AsObjectArg<T>` for object parameters. Only set if requested and `T` is an engine class.
pub fn new_no_defaults(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam {
FnParam {
name: safe_ident(&method_arg.name),
Expand Down Expand Up @@ -616,8 +617,15 @@ pub enum RustTy {

/// `Gd<Node>`
EngineClass {
/// Tokens with full `Gd<T>`
/// Tokens with full `Gd<T>` (e.g. used in return type position).
tokens: TokenStream,

/// Tokens with `ObjectArg<T>` (used in `type CallSig` tuple types).
arg_view: TokenStream,

/// Signature declaration with `impl AsObjectArg<T>`.
impl_as_arg: TokenStream,

/// only inner `T`
#[allow(dead_code)] // only read in minimal config
inner_class: Ident,
Expand All @@ -628,6 +636,15 @@ pub enum RustTy {
}

impl RustTy {
pub fn param_decl(&self) -> TokenStream {
match self {
RustTy::EngineClass {
arg_view: raw_gd, ..
} => raw_gd.clone(),
other => other.to_token_stream(),
}
}

pub fn return_decl(&self) -> TokenStream {
match self {
Self::EngineClass { tokens, .. } => quote! { -> Option<#tokens> },
Expand Down
2 changes: 1 addition & 1 deletion godot-codegen/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn make_imports() -> TokenStream {
use crate::meta::{ClassName, PtrcallSignatureTuple, VarcallSignatureTuple};
use crate::classes::native::*;
use crate::classes::Object;
use crate::obj::Gd;
use crate::obj::{Gd, ObjectArg, AsObjectArg};
use crate::sys::GodotFfi as _;
}
}
Expand Down
3 changes: 3 additions & 0 deletions godot-core/src/builtin/variant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ pub struct Variant {

impl Variant {
/// Create an empty variant (`null` value in GDScript).
///
/// If a Godot engine API accepts object (not variant) parameters and you'd like to pass `null`, use
/// [`Gd::null_arg()`][crate::obj::Gd::null_arg] instead.
pub fn nil() -> Self {
Self::default()
}
Expand Down
3 changes: 2 additions & 1 deletion godot-core/src/meta/sealed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ impl Sealed for f32 {}
impl Sealed for () {}
impl Sealed for Variant {}
impl<T: ArrayElement> Sealed for Array<T> {}
impl<T: GodotClass> Sealed for RawGd<T> {}
impl<T: GodotClass> Sealed for Gd<T> {}
impl<T: GodotClass> Sealed for RawGd<T> {}
impl<T: GodotClass> Sealed for ObjectArg<T> {}
impl<T> Sealed for Option<T>
where
T: GodotType,
Expand Down
1 change: 1 addition & 0 deletions godot-core/src/meta/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub trait GodotFfiVariant: Sized + GodotFfi {
// this type to indicate that they are 32 bits large.
pub trait GodotType:
GodotConvert<Via = Self> + ToGodot + FromGodot + sealed::Sealed + 'static
// 'static is not technically required, but it simplifies a few things (limits e.g. ObjectArg).
{
#[doc(hidden)]
type Ffi: GodotFfiVariant;
Expand Down
Loading
Loading