Skip to content

Signals now accept parameters #279

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

Merged
merged 2 commits into from
Jun 6, 2023
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
3 changes: 2 additions & 1 deletion godot-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ pub mod private {
pub use crate::registry::{callbacks, ClassPlugin, ErasedRegisterFn, PluginComponent};
pub use crate::storage::as_storage;
pub use crate::{
gdext_register_method, gdext_register_method_inner, gdext_virtual_method_callback,
gdext_get_arguments_info, gdext_register_method, gdext_register_method_inner,
gdext_virtual_method_callback,
};

use crate::{log, sys};
Expand Down
39 changes: 24 additions & 15 deletions godot-core/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,20 +229,7 @@ macro_rules! gdext_register_method_inner {

// Arguments meta-information
let argument_count = NUM_ARGS as u32;
let mut arguments_info: [PropertyInfo; NUM_ARGS] = {
let mut i = -1i32;
[$(
{
i += 1;
let prop = Sig::property_info(i, stringify!($param));
//OnceArg::new(prop)
prop
},
)*]
};
let mut arguments_info_sys: [sys::GDExtensionPropertyInfo; NUM_ARGS]
= std::array::from_fn(|i| arguments_info[i].property_sys());
// = std::array::from_fn(|i| arguments_info[i].once_sys());
let mut arguments_info: [sys::GDExtensionPropertyInfo; NUM_ARGS] = $crate::gdext_get_arguments_info!(($($RetTy)+, $($ParamTy),*), $( $param, )*);
let mut arguments_metadata: [sys::GDExtensionClassMethodArgumentMetadata; NUM_ARGS]
= std::array::from_fn(|i| Sig::param_metadata(i as i32));

Expand All @@ -262,7 +249,7 @@ macro_rules! gdext_register_method_inner {
return_value_info: std::ptr::addr_of_mut!(return_value_info_sys),
return_value_metadata,
argument_count,
arguments_info: arguments_info_sys.as_mut_ptr(),
arguments_info: arguments_info.as_mut_ptr(),
arguments_metadata: arguments_metadata.as_mut_ptr(),
default_argument_count: 0,
default_arguments: std::ptr::null_mut(),
Expand Down Expand Up @@ -621,3 +608,25 @@ macro_rules! gdext_ptrcall {
);
};
}

#[doc(hidden)]
#[macro_export]
macro_rules! gdext_get_arguments_info {
(
$Signature:ty,
$($param:ident,)*
) => {
{
use $crate::builtin::meta::*;

let mut i = -1i32;
[$(
{
i += 1;
let prop = <$Signature as VarcallSignatureTuple>::property_info(i, stringify!($param)).property_sys();
prop
},
)*]
}
};
}
54 changes: 40 additions & 14 deletions godot-macros/src/godot_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::util;
use crate::util::bail;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use venial::{AttributeValue, Declaration, Error, Function, Impl, ImplMember};
use venial::{AttributeValue, Declaration, Error, FnParam, Function, Impl, ImplMember, TyExpr};

pub fn transform(input_decl: Declaration) -> Result<TokenStream, Error> {
let decl = match input_decl {
Expand Down Expand Up @@ -62,19 +62,41 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
let class_name = util::validate_impl(&decl, None, "godot_api")?;
let class_name_str = class_name.to_string();

//let register_fn = format_ident!("__godot_rust_register_{}", class_name_str);
//#[allow(non_snake_case)]

let (funcs, signals) = process_godot_fns(&mut decl)?;
let signal_name_strs = signals.into_iter().map(|ident| ident.to_string());

let mut signal_name_strs: Vec<String> = Vec::new();
let mut signal_parameters_count: Vec<i64> = Vec::new();
let mut signal_parameters: Vec<TokenStream> = Vec::new();

for signature in signals {
let mut param_types: Vec<TyExpr> = Vec::new();
let mut param_names: Vec<Ident> = Vec::new();

for param in signature.params.inner {
match &param.0 {
FnParam::Typed(param) => {
param_types.push(param.ty.clone());
param_names.push(param.name.clone());
}
FnParam::Receiver(_) => {}
};
}

signal_name_strs.push(signature.name.to_string());
signal_parameters_count.push(param_names.len() as i64);
signal_parameters.push(
quote! {
::godot::private::gdext_get_arguments_info!(((), #(#param_types ),*), #(#param_names, )*).as_ptr()
},
);
}

let prv = quote! { ::godot::private };

let result = quote! {
#decl

impl ::godot::obj::cap::ImplementsGodotApi for #class_name {
//fn __register_methods(_builder: &mut ::godot::builder::ClassBuilder<Self>) {
fn __register_methods() {
#(
::godot::private::gdext_register_method!(#class_name, #funcs);
Expand All @@ -83,14 +105,17 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
unsafe {
let class_name = ::godot::builtin::StringName::from(#class_name_str);
use ::godot::sys;

#(
let parameters = #signal_parameters;
let signal_name = ::godot::builtin::StringName::from(#signal_name_strs);

sys::interface_fn!(classdb_register_extension_class_signal)(
sys::get_library(),
class_name.string_sys(),
signal_name.string_sys(),
std::ptr::null(), // NULL only valid for zero parameters, in current impl; maybe better empty slice
0,
parameters,
sys::GDExtensionInt::from(#signal_parameters_count),
);
)*
}
Expand All @@ -110,9 +135,9 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
Ok(result)
}

fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<Function>, Vec<Ident>), Error> {
fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<Function>, Vec<Function>), Error> {
let mut func_signatures = vec![];
let mut signal_idents = vec![]; // TODO consider signature
let mut signal_signatures = vec![];

let mut removed_indexes = vec![];
for (index, item) in decl.body_items.iter_mut().enumerate() {
Expand Down Expand Up @@ -147,11 +172,12 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<Function>, Vec<Ident>), Err
func_signatures.push(sig);
}
BoundAttrType::Signal(ref _attr_val) => {
if !method.params.is_empty() || method.return_ty.is_some() {
return attr.bail("parameters and return types not yet supported", method);
if method.return_ty.is_some() {
return attr.bail("return types are not supported", method);
}
let sig = util::reduce_to_signature(method);

signal_idents.push(method.name.clone());
signal_signatures.push(sig.clone());
removed_indexes.push(index);
}
}
Expand All @@ -164,7 +190,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<Function>, Vec<Ident>), Err
decl.body_items.remove(index);
}

Ok((func_signatures, signal_idents))
Ok((func_signatures, signal_signatures))
}

fn extract_attributes(method: &Function) -> Result<Option<BoundAttr>, Error> {
Expand Down
1 change: 1 addition & 0 deletions itest/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ mod projection_test;
mod quaternion_test;
mod rect2i_test;
mod rid_test;
mod signal_test;
mod singleton_test;
mod string;
mod transform2d_test;
Expand Down
97 changes: 97 additions & 0 deletions itest/rust/src/signal_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* 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/.
*/

use std::cell::Cell;

use godot::bind::{godot_api, GodotClass};
use godot::builtin::{GodotString, Variant};

use godot::engine::Object;
use godot::obj::{Base, Gd, Share};
use godot::sys;

use crate::itest;

#[derive(GodotClass)]
#[class(init, base=Object)]
struct Emitter {
#[base]
base: Base<Object>,
}

#[godot_api]
impl Emitter {
#[signal]
fn signal_0_arg();
#[signal]
fn signal_1_arg(arg1: i64);
#[signal]
fn signal_2_arg(arg1: Gd<Object>, arg2: GodotString);
}

#[derive(GodotClass)]
#[class(init, base=Object)]
struct Receiver {
used: [Cell<bool>; 3],
#[base]
base: Base<Object>,
}

#[godot_api]
impl Receiver {
#[func]
fn receive_0_arg(&self) {
self.used[0].set(true);
}
#[func]
fn receive_1_arg(&self, arg1: i64) {
self.used[1].set(true);
assert_eq!(arg1, 987);
}
#[func]
fn receive_2_arg(&self, arg1: Gd<Object>, arg2: GodotString) {
assert_eq!(self.base.share(), arg1);
assert_eq!(SIGNAL_ARG_STRING, arg2.to_string());

self.used[2].set(true);
}
}

const SIGNAL_ARG_STRING: &str = "Signal string arg";

#[itest]
/// Test that godot can call a method that is connect with a signal
fn signals() {
let mut emitter = Gd::<Emitter>::new_default();
let receiver = Gd::<Receiver>::new_default();

let args = [
vec![],
vec![Variant::from(987)],
vec![
Variant::from(receiver.share()),
Variant::from(SIGNAL_ARG_STRING),
],
];

for (i, arg) in args.iter().enumerate() {
let signal_name = format!("signal_{i}_arg");
let receiver_name = format!("receive_{i}_arg");

emitter.bind_mut().connect(
signal_name.clone().into(),
receiver.callable(receiver_name),
0,
);

emitter.bind_mut().emit_signal(signal_name.into(), arg);

assert!(receiver.bind().used[i].get());
}

receiver.free();
emitter.free();
}