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

Default func params from Godot #380

Closed
Closed
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
13 changes: 10 additions & 3 deletions godot-core/src/builtin/meta/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub trait VarcallSignatureTuple: PtrcallSignatureTuple {
unsafe fn varcall(
instance_ptr: sys::GDExtensionClassInstancePtr,
args_ptr: *const sys::GDExtensionConstVariantPtr,
arg_count: sys::GDExtensionInt,
ret: sys::GDExtensionVariantPtr,
err: *mut sys::GDExtensionCallError,
func: fn(sys::GDExtensionClassInstancePtr, Self::Params) -> Self::Ret,
Expand Down Expand Up @@ -75,7 +76,7 @@ macro_rules! impl_varcall_signature_for_tuple {
#[allow(unused_variables)]
impl<$R, $($Pn,)*> VarcallSignatureTuple for ($R, $($Pn,)*)
where $R: VariantMetadata + ToVariant + sys::GodotFuncMarshal + Debug,
$( $Pn: VariantMetadata + FromVariant + sys::GodotFuncMarshal + Debug, )*
$( $Pn: VariantMetadata + FromVariant + sys::GodotFuncMarshal + Debug + Default, )*
{
const PARAM_COUNT: usize = $PARAM_COUNT;

Expand Down Expand Up @@ -106,6 +107,7 @@ macro_rules! impl_varcall_signature_for_tuple {
unsafe fn varcall(
instance_ptr: sys::GDExtensionClassInstancePtr,
args_ptr: *const sys::GDExtensionConstVariantPtr,
arg_count: sys::GDExtensionInt,
ret: sys::GDExtensionVariantPtr,
err: *mut sys::GDExtensionCallError,
func: fn(sys::GDExtensionClassInstancePtr, Self::Params) -> Self::Ret,
Expand All @@ -114,7 +116,7 @@ macro_rules! impl_varcall_signature_for_tuple {
$crate::out!("varcall: {}", method_name);

let args = ($(
unsafe { varcall_arg::<$Pn, $n>(args_ptr, method_name) },
unsafe { varcall_arg::<$Pn, $n>(args_ptr, arg_count as isize, method_name) },
)*) ;

varcall_return::<$R>(func(instance_ptr, args), ret, err)
Expand Down Expand Up @@ -163,10 +165,14 @@ macro_rules! impl_ptrcall_signature_for_tuple {
///
/// # Safety
/// - It must be safe to dereference the pointer at `args_ptr.offset(N)` .
unsafe fn varcall_arg<P: FromVariant, const N: isize>(
unsafe fn varcall_arg<P: FromVariant + Default, const N: isize>(
args_ptr: *const sys::GDExtensionConstVariantPtr,
arg_count: isize,
method_name: &str,
) -> P {
if arg_count >= N {
return P::default();
}
let variant = &*(*args_ptr.offset(N) as *mut Variant); // TODO from_var_sys
P::try_from_variant(variant)
.unwrap_or_else(|_| param_error::<P>(method_name, N as i32, variant))
Expand Down Expand Up @@ -199,6 +205,7 @@ unsafe fn ptrcall_arg<P: sys::GodotFuncMarshal, const N: isize>(
method_name: &str,
call_type: sys::PtrcallType,
) -> P {
println!("ptrcall - {method_name}");
P::try_from_arg(sys::force_mut_ptr(*args_ptr.offset(N)), call_type)
.unwrap_or_else(|e| param_error::<P>(method_name, N as i32, &e))
}
Expand Down
9 changes: 2 additions & 7 deletions godot-macros/src/derive_godot_class/property/field_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,8 @@ impl GetterSetterImpl {
};

let signature = util::parse_signature(signature);
let export_token = make_method_registration(
class_name,
FuncDefinition {
func: signature,
rename: None,
},
);
let export_token =
make_method_registration(class_name, FuncDefinition::from_signature(signature));

Self {
function_name,
Expand Down
37 changes: 31 additions & 6 deletions godot-macros/src/godot_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ pub fn transform(input_decl: Declaration) -> Result<TokenStream, Error> {

/// Attribute for user-declared function
enum BoundAttrType {
Func { rename: Option<String> },
Func {
rename: Option<String>,
default_params: Option<Vec<String>>,
},
Signal(AttributeValue),
Const(AttributeValue),
}
Expand Down Expand Up @@ -232,10 +235,17 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Functi
}

match attr.ty {
BoundAttrType::Func { rename } => {
BoundAttrType::Func {
rename,
default_params,
} => {
// Signatures are the same thing without body
let sig = util::reduce_to_signature(method);
func_definitions.push(FuncDefinition { func: sig, rename });
func_definitions.push(FuncDefinition {
func: sig,
rename,
default_params,
});
}
BoundAttrType::Signal(ref _attr_val) => {
if method.return_ty.is_some() {
Expand Down Expand Up @@ -312,17 +322,32 @@ where

let new_found = match attr_name {
name if name == "func" => {
// TODO you-win (August 8, 2023): handle default values here as well?

// Safe unwrap since #[func] must be present if we got to this point
let mut parser = KvParser::parse(attributes, "func")?.unwrap();

let rename = parser.handle_expr("rename")?.map(|ts| ts.to_string());
let default_params: Option<Vec<String>> =
if let Some(mut list_parser) = parser.handle_array("defaults")? {
let mut defaults = vec![];

while let Ok(expr) = list_parser.next_expr() {
defaults.push(expr.to_string());
}

list_parser.finish()?;

Some(defaults)
} else {
None
};

Some(BoundAttr {
attr_name: attr_name.clone(),
index,
ty: BoundAttrType::Func { rename },
ty: BoundAttrType::Func {
rename,
default_params,
},
})
}
name if name == "signal" => {
Expand Down
52 changes: 33 additions & 19 deletions godot-macros/src/method_registration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ pub struct FuncDefinition {
pub func: venial::Function,
/// The name the function will be exposed as in Godot. If `None`, the Rust function name is used.
pub rename: Option<String>,
// either all params must have default args or no params have default args
/// Default parameters in sequential order.
pub default_params: Option<Vec<String>>,
}

impl FuncDefinition {
/// Convenience function for struct creation with _only_ a signature.
pub fn from_signature(signature: venial::Function) -> Self {
FuncDefinition {
func: signature,
rename: None,
default_params: None,
}
}
}

/// Returns a closure expression that forwards the parameters to the Rust instance.
Expand Down Expand Up @@ -161,22 +175,22 @@ fn make_ptrcall_invocation(
}
}

/// Generate code for a `varcall()` call expression.
fn make_varcall_invocation(
method_name: &Ident,
sig_tuple: &TokenStream,
wrapped_method: &TokenStream,
) -> TokenStream {
let method_name_str = method_name.to_string();

quote! {
<#sig_tuple as ::godot::builtin::meta::VarcallSignatureTuple>::varcall(
instance_ptr,
args,
ret,
err,
#wrapped_method,
#method_name_str,
)
}
}
//// Generate code for a `varcall()` call expression.
// fn make_varcall_invocation(
// method_name: &Ident,
// sig_tuple: &TokenStream,
// wrapped_method: &TokenStream,
// ) -> TokenStream {
// let method_name_str = method_name.to_string();

// quote! {
// <#sig_tuple as ::godot::builtin::meta::VarcallSignatureTuple>::varcall(
// instance_ptr,
// args,
// ret,
// err,
// #wrapped_method,
// #method_name_str,
// )
// }
// }
45 changes: 37 additions & 8 deletions godot-macros/src/method_registration/register_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
*/

use crate::method_registration::{
get_signature_info, make_forwarding_closure, make_method_flags, make_ptrcall_invocation,
make_varcall_invocation,
get_signature_info,
make_forwarding_closure,
make_method_flags,
make_ptrcall_invocation,
// make_varcall_invocation,
};
use crate::util;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use quote::{quote, ToTokens};

/// Generates code that registers the specified method for the given class.
pub fn make_method_registration(
Expand Down Expand Up @@ -39,10 +42,18 @@ pub fn make_method_registration(
method_name.to_string()
};
let param_ident_strs = param_idents.iter().map(|ident| ident.to_string());
// TODO you-win August 12, 2023: Generate *_ex methods from this somehow?
// TODO you-win August 12, 2023: Definitely not working, causes Godot to hang during itests
let default_params = func_definition.default_params.map_or(vec![], |params| {
params
.into_iter()
.map(|param| param.to_token_stream())
.collect::<Vec<TokenStream>>()
});

quote! {
{
use ::godot::obj::GodotClass;
use ::godot::obj::{Gd, GodotClass};
use ::godot::builtin::meta::registration::method::MethodInfo;
use ::godot::builtin::{StringName, Variant};
use ::godot::sys;
Expand All @@ -54,6 +65,8 @@ pub fn make_method_registration(
let varcall_func = #varcall_func;
let ptrcall_func = #ptrcall_func;

let default_params = vec![ #( Variant::from( #default_params ) ),* ];

// SAFETY:
// `get_varcall_func` upholds all the requirements for `call_func`.
// `get_ptrcall_func` upholds all the requirements for `ptrcall_func`
Expand All @@ -67,7 +80,8 @@ pub fn make_method_registration(
&[
#( #param_ident_strs ),*
],
Vec::new()
// Vec::new()
default_params
)
};

Expand All @@ -89,23 +103,38 @@ fn make_varcall_func(
sig_tuple: &TokenStream,
wrapped_method: &TokenStream,
) -> TokenStream {
let invocation = make_varcall_invocation(method_name, sig_tuple, wrapped_method);
// let invocation = make_varcall_invocation(method_name, sig_tuple, wrapped_method);
let method_name_str = method_name.to_string();

quote! {
{
unsafe extern "C" fn function(
_method_data: *mut std::ffi::c_void,
instance_ptr: sys::GDExtensionClassInstancePtr,
args: *const sys::GDExtensionConstVariantPtr,
_arg_count: sys::GDExtensionInt,
arg_count: sys::GDExtensionInt,
ret: sys::GDExtensionVariantPtr,
err: *mut sys::GDExtensionCallError,
) {
// let success = ::godot::private::handle_panic(
// || stringify!(#method_name),
// || #invocation
// );
let success = ::godot::private::handle_panic(
|| stringify!(#method_name),
|| #invocation
|| <#sig_tuple as ::godot::builtin::meta::VarcallSignatureTuple>::varcall(
instance_ptr,
args,
arg_count,
ret,
err,
#wrapped_method,
#method_name_str
)
);

// println!("make_varcall_func arg_count - {_arg_count:?}");

if success.is_none() {
// Signal error and set return type to Nil
(*err).error = sys::GDEXTENSION_CALL_ERROR_INVALID_METHOD; // no better fitting enum?
Expand Down
10 changes: 10 additions & 0 deletions itest/godot/ManualFfiTests.gd
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,13 @@ func test_func_rename():
assert_eq(func_rename.has_method("renamed_static"), false)
assert_eq(func_rename.has_method("spell_static"), true)
assert_eq(func_rename.spell_static(), "static")

func test_default_params_primitives():
var tester := DefaultParamsPrimitives.new()

assert_eq(tester.add_ints(5, 6), 11)
assert_eq(tester.add_ints(5), 5)
assert_eq(tester.add_ints(), 0)

assert_eq(tester.pass_string(), "hello")
assert_eq(tester.pass_string("world"), "world")
26 changes: 26 additions & 0 deletions itest/rust/src/func_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,29 @@ impl RefCountedVirtual for FuncRename {
Self
}
}

// TODO you-win August 12, 2023: impl to/from variant for option<T>?

#[derive(GodotClass)]
#[class(base=RefCounted)]
struct DefaultParamsPrimitives;

#[godot_api]
impl DefaultParamsPrimitives {
#[func(defaults = [1, 2])]
fn add_ints(&self, a: i32, b: i32) -> i32 {
a + b
}

#[func(defaults = ["hello"])]
fn pass_string(&self, text: GodotString) -> GodotString {
text
}
}

#[godot_api]
impl RefCountedVirtual for DefaultParamsPrimitives {
fn init(_base: Base<Self::Base>) -> Self {
Self
}
}
Loading