Skip to content

Commit 91c1391

Browse files
authored
Support specifying signal argument names (#76)
1 parent a3e748c commit 91c1391

File tree

5 files changed

+150
-58
lines changed

5 files changed

+150
-58
lines changed

derive/src/attribute_ops.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ pub struct FieldExportOps {
3333
custom_type: Option<WithOriginal<LitStr, Meta>>,
3434
}
3535

36+
#[derive(FromMeta, Debug)]
37+
pub struct FieldSignalOps(pub WithOriginal<Vec<syn::LitStr>, Meta>);
38+
3639
impl FieldExportOps {
3740
pub fn hint(&self, ty: &Type) -> Result<(TokenStream, TokenStream), TokenStream> {
3841
let godot_types = godot_types();

derive/src/lib.rs

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ mod impl_attribute;
1010
mod type_paths;
1111

1212
use attribute_ops::{FieldOpts, GodotScriptOpts};
13-
use darling::{util::SpannedValue, FromAttributes, FromDeriveInput};
13+
use darling::{util::SpannedValue, FromAttributes, FromDeriveInput, FromMeta};
1414
use itertools::Itertools;
1515
use proc_macro2::TokenStream;
1616
use quote::{quote, quote_spanned, ToTokens};
1717
use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Ident, Type};
1818
use type_paths::{godot_types, property_hints, string_name_ty, variant_ty};
1919

20-
use crate::attribute_ops::{FieldExportOps, PropertyOpts};
20+
use crate::attribute_ops::{FieldExportOps, FieldSignalOps, PropertyOpts};
2121

2222
#[proc_macro_derive(GodotScript, attributes(export, script, prop, signal))]
2323
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
@@ -47,7 +47,7 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
4747
export_field_state,
4848
): (
4949
TokenStream,
50-
TokenStream,
50+
(TokenStream, TokenStream),
5151
TokenStream,
5252
TokenStream,
5353
TokenStream,
@@ -92,12 +92,12 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9292
(is_public && !is_signal).then(|| derive_property_state_export(field));
9393

9494
let signal_metadata = match (is_public, is_signal) {
95-
(false, false) | (true, false) => TokenStream::default(),
95+
(false, false) | (true, false) => (TokenStream::default(), TokenStream::default()),
9696
(true, true) => derive_signal_metadata(field),
9797
(false, true) => {
9898
let err = compile_error("Signals must be public!", signal_attr);
9999

100-
quote! {#err,}
100+
(quote! {#err,}, TokenStream::default())
101101
}
102102
};
103103

@@ -133,6 +133,8 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
133133
acc
134134
});
135135

136+
let (signal_metadata, signal_const_assert) = signal_metadata;
137+
136138
let output = quote! {
137139
impl ::godot_rust_script::GodotScript for #script_type_ident {
138140
type Base = #base_class;
@@ -156,6 +158,8 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
156158
#default_impl
157159
}
158160

161+
#signal_const_assert
162+
159163
::godot_rust_script::register_script_class!(
160164
#script_type_ident,
161165
#base_class,
@@ -433,22 +437,67 @@ fn get_field_description(field: &FieldOpts) -> Option<TokenStream> {
433437
})
434438
}
435439

436-
fn derive_signal_metadata(field: &SpannedValue<FieldOpts>) -> TokenStream {
440+
fn derive_signal_metadata(field: &SpannedValue<FieldOpts>) -> (TokenStream, TokenStream) {
437441
let signal_name = field
438442
.ident
439443
.as_ref()
440444
.map(|ident| ident.to_string())
441445
.unwrap_or_default();
442446
let signal_description = get_field_description(field);
443447
let signal_type = &field.ty;
448+
let signal_ops = match field
449+
.attrs
450+
.iter()
451+
.find(|attr| attr.path().is_ident("signal"))
452+
.and_then(|attr| match &attr.meta {
453+
syn::Meta::Path(_) => None,
454+
syn::Meta::List(_) => Some(FieldSignalOps::from_meta(&attr.meta)),
455+
syn::Meta::NameValue(_) => Some(Err(darling::Error::custom(
456+
"Signal attribute does not support assigning a value!",
457+
)
458+
.with_span(&attr.meta))),
459+
})
460+
.transpose()
461+
{
462+
Ok(ops) => ops,
463+
Err(err) => return (TokenStream::default(), err.write_errors()),
464+
};
444465

445-
quote! {
466+
let const_assert = signal_ops.as_ref().map(|ops| {
467+
let count = ops.0.parsed.len();
468+
469+
quote_spanned! { ops.0.original.span() =>
470+
const _: () = {
471+
assert!(<#signal_type>::ARG_COUNT == #count as u8, "argument names do not match number of arguments.");
472+
};
473+
}
474+
});
475+
476+
let argument_names = signal_ops
477+
.map(|names| {
478+
let span = names.0.original.span();
479+
#[expect(unstable_name_collisions)]
480+
let names: TokenStream = names
481+
.0
482+
.parsed
483+
.iter()
484+
.map(|name| name.to_token_stream())
485+
.intersperse(quote!(,).into_token_stream())
486+
.collect();
487+
488+
quote_spanned! { span => Some(&[#names]) }
489+
})
490+
.unwrap_or_else(|| quote!(None));
491+
492+
let metadata = quote! {
446493
::godot_rust_script::private_export::RustScriptSignalDesc {
447494
name: #signal_name,
448-
arguments: <#signal_type as ::godot_rust_script::ScriptSignal>::argument_desc(),
495+
arguments: <#signal_type>::argument_desc(#argument_names),
449496
description: concat!(#signal_description),
450497
},
451-
}
498+
};
499+
500+
(metadata, const_assert.unwrap_or_default())
452501
}
453502

454503
#[proc_macro_attribute]

rust-script/src/interface.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use godot::prelude::{ConvertError, Gd, Object, StringName, Variant};
1818
pub use crate::runtime::Context;
1919

2020
pub use export::GodotScriptExport;
21+
#[expect(deprecated)]
2122
pub use signals::{ScriptSignal, Signal};
2223

2324
pub trait GodotScript: Debug + GodotScriptImpl<ImplBase = Self::Base> {

rust-script/src/interface/signals.rs

Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,32 @@
77
use std::marker::PhantomData;
88

99
use godot::builtin::{
10-
Callable, Dictionary, GString, NodePath, StringName, Variant, Vector2, Vector3,
10+
Callable, Dictionary, GString, NodePath, StringName, Variant, Vector2, Vector3, Vector4,
1111
};
1212
use godot::classes::Object;
1313
use godot::global::{Error, PropertyHint};
1414
use godot::meta::{GodotConvert, GodotType, ToGodot};
15-
use godot::obj::Gd;
15+
use godot::obj::{Gd, GodotClass};
1616

1717
use crate::static_script_registry::RustScriptPropDesc;
18-
19-
pub trait ScriptSignal {
20-
type Args: SignalArguments;
21-
22-
fn new(host: Gd<Object>, name: &'static str) -> Self;
23-
24-
fn emit(&self, args: Self::Args);
25-
26-
fn connect(&mut self, callable: Callable) -> Result<(), Error>;
27-
28-
fn argument_desc() -> Box<[RustScriptPropDesc]>;
29-
30-
fn name(&self) -> &str;
31-
}
18+
use crate::{GodotScript, RsRef};
3219

3320
pub trait SignalArguments {
34-
fn count() -> u8;
21+
const COUNT: u8;
3522

3623
fn to_variants(&self) -> Vec<Variant>;
3724

38-
fn argument_desc() -> Box<[RustScriptPropDesc]>;
25+
fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]>;
3926
}
4027

4128
impl SignalArguments for () {
42-
fn count() -> u8 {
43-
0
44-
}
29+
const COUNT: u8 = 0;
4530

4631
fn to_variants(&self) -> Vec<Variant> {
4732
vec![]
4833
}
4934

50-
fn argument_desc() -> Box<[RustScriptPropDesc]> {
35+
fn argument_desc(_arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
5136
Box::new([])
5237
}
5338
}
@@ -60,9 +45,7 @@ macro_rules! count_tts {
6045
macro_rules! tuple_args {
6146
(impl $($arg: ident),+) => {
6247
impl<$($arg: ToGodot),+> SignalArguments for ($($arg,)+) {
63-
fn count() -> u8 {
64-
count_tts!($($arg)+)
65-
}
48+
const COUNT: u8 = count_tts!($($arg)+);
6649

6750
fn to_variants(&self) -> Vec<Variant> {
6851
#[allow(non_snake_case)]
@@ -73,9 +56,12 @@ macro_rules! tuple_args {
7356
]
7457
}
7558

76-
fn argument_desc() -> Box<[RustScriptPropDesc]> {
59+
fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
60+
#[expect(non_snake_case)]
61+
let [$($arg),+] = arg_names.unwrap_or(&[$(stringify!($arg)),+]).try_into().unwrap(); //.unwrap_or_else(|| [$(stringify!($arg)),+]);
62+
7763
Box::new([
78-
$(signal_argument_desc!("0", $arg)),+
64+
$(signal_argument_desc!($arg, $arg)),+
7965
])
8066
}
8167
}
@@ -98,17 +84,17 @@ macro_rules! tuple_args {
9884
macro_rules! single_args {
9985
(impl $arg: ty) => {
10086
impl SignalArguments for $arg {
101-
fn count() -> u8 {
102-
1
103-
}
87+
const COUNT: u8 = 1;
10488

10589
fn to_variants(&self) -> Vec<Variant> {
10690
vec![self.to_variant()]
10791
}
10892

109-
fn argument_desc() -> Box<[RustScriptPropDesc]> {
93+
fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
94+
let [arg_name] = arg_names.unwrap_or_else(|| &["0"]).try_into().unwrap();
95+
11096
Box::new([
111-
signal_argument_desc!("0", $arg),
97+
signal_argument_desc!(arg_name, $arg),
11298
])
11399
}
114100
}
@@ -120,7 +106,7 @@ macro_rules! single_args {
120106
}
121107

122108
macro_rules! signal_argument_desc {
123-
($name:literal, $type:ty) => {
109+
($name:expr, $type:ty) => {
124110
RustScriptPropDesc {
125111
name: $name,
126112
ty: <<<$type as GodotConvert>::Via as GodotType>::Ffi as godot::sys::GodotFfi>::VARIANT_TYPE.variant_as_nil(),
@@ -135,55 +121,96 @@ macro_rules! signal_argument_desc {
135121

136122
tuple_args!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10);
137123
single_args!(
138-
bool, u8, u16, u32, u64, i8, i16, i32, i64, f64, GString, StringName, NodePath, Vector2,
139-
Vector3, Dictionary
124+
bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, GString, StringName, NodePath, Vector2,
125+
Vector3, Vector4, Dictionary
140126
);
141127

128+
impl<T: GodotClass> SignalArguments for Gd<T> {
129+
const COUNT: u8 = 1;
130+
131+
fn to_variants(&self) -> Vec<Variant> {
132+
vec![self.to_variant()]
133+
}
134+
135+
fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
136+
let name = arg_names
137+
.and_then(|list| list.first())
138+
.copied()
139+
.unwrap_or("0");
140+
141+
Box::new([signal_argument_desc!(name, Self)])
142+
}
143+
}
144+
145+
impl<T: GodotScript> SignalArguments for RsRef<T> {
146+
const COUNT: u8 = 1;
147+
148+
fn to_variants(&self) -> Vec<Variant> {
149+
vec![self.to_variant()]
150+
}
151+
152+
fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
153+
Box::new([signal_argument_desc!(
154+
arg_names
155+
.and_then(|list| list.first())
156+
.copied()
157+
.unwrap_or("0"),
158+
Self
159+
)])
160+
}
161+
}
162+
142163
#[derive(Debug)]
143-
pub struct Signal<T: SignalArguments> {
164+
pub struct ScriptSignal<T: SignalArguments> {
144165
host: Gd<Object>,
145166
name: &'static str,
146167
args: PhantomData<T>,
147168
}
148169

149-
impl<T: SignalArguments> ScriptSignal for Signal<T> {
150-
type Args = T;
170+
#[deprecated(
171+
note = "The Signal type has been deprecated and will be removed soon. Please use the ScriptSignal instead."
172+
)]
173+
pub type Signal<T> = ScriptSignal<T>;
174+
175+
impl<T: SignalArguments> ScriptSignal<T> {
176+
pub const ARG_COUNT: u8 = T::COUNT;
151177

152-
fn new(host: Gd<Object>, name: &'static str) -> Self {
178+
pub fn new(host: Gd<Object>, name: &'static str) -> Self {
153179
Self {
154180
host,
155181
name,
156182
args: PhantomData,
157183
}
158184
}
159185

160-
fn emit(&self, args: Self::Args) {
186+
pub fn emit(&self, args: T) {
161187
self.host
162188
.clone()
163189
.emit_signal(self.name, &args.to_variants());
164190
}
165191

166-
fn connect(&mut self, callable: Callable) -> Result<(), Error> {
192+
pub fn connect(&mut self, callable: Callable) -> Result<(), Error> {
167193
match self.host.connect(self.name, &callable) {
168194
Error::OK => Ok(()),
169195
error => Err(error),
170196
}
171197
}
172198

173-
fn argument_desc() -> Box<[RustScriptPropDesc]> {
174-
<T as SignalArguments>::argument_desc()
199+
#[doc(hidden)]
200+
pub fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
201+
<T as SignalArguments>::argument_desc(arg_names)
175202
}
176203

177-
fn name(&self) -> &str {
204+
pub fn name(&self) -> &str {
178205
self.name
179206
}
180207
}
181208

182-
impl<T: SignalArguments> GodotConvert for Signal<T> {
209+
impl<T: SignalArguments> GodotConvert for ScriptSignal<T> {
183210
type Via = godot::builtin::Signal;
184211
}
185212

186-
impl<T: SignalArguments> ToGodot for Signal<T> {
213+
impl<T: SignalArguments> ToGodot for ScriptSignal<T> {
187214
type ToVia<'v>
188215
= Self::Via
189216
where

0 commit comments

Comments
 (0)