Skip to content
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: 3 additions & 0 deletions derive/src/attribute_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub struct FieldExportOps {
custom_type: Option<WithOriginal<LitStr, Meta>>,
}

#[derive(FromMeta, Debug)]
pub struct FieldSignalOps(pub WithOriginal<Vec<syn::LitStr>, Meta>);

impl FieldExportOps {
pub fn hint(&self, ty: &Type) -> Result<(TokenStream, TokenStream), TokenStream> {
let godot_types = godot_types();
Expand Down
67 changes: 58 additions & 9 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ mod impl_attribute;
mod type_paths;

use attribute_ops::{FieldOpts, GodotScriptOpts};
use darling::{util::SpannedValue, FromAttributes, FromDeriveInput};
use darling::{util::SpannedValue, FromAttributes, FromDeriveInput, FromMeta};
use itertools::Itertools;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Ident, Type};
use type_paths::{godot_types, property_hints, string_name_ty, variant_ty};

use crate::attribute_ops::{FieldExportOps, PropertyOpts};
use crate::attribute_ops::{FieldExportOps, FieldSignalOps, PropertyOpts};

#[proc_macro_derive(GodotScript, attributes(export, script, prop, signal))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
Expand Down Expand Up @@ -47,7 +47,7 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
export_field_state,
): (
TokenStream,
TokenStream,
(TokenStream, TokenStream),
TokenStream,
TokenStream,
TokenStream,
Expand Down Expand Up @@ -92,12 +92,12 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
(is_public && !is_signal).then(|| derive_property_state_export(field));

let signal_metadata = match (is_public, is_signal) {
(false, false) | (true, false) => TokenStream::default(),
(false, false) | (true, false) => (TokenStream::default(), TokenStream::default()),
(true, true) => derive_signal_metadata(field),
(false, true) => {
let err = compile_error("Signals must be public!", signal_attr);

quote! {#err,}
(quote! {#err,}, TokenStream::default())
}
};

Expand Down Expand Up @@ -133,6 +133,8 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
acc
});

let (signal_metadata, signal_const_assert) = signal_metadata;

let output = quote! {
impl ::godot_rust_script::GodotScript for #script_type_ident {
type Base = #base_class;
Expand All @@ -156,6 +158,8 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
#default_impl
}

#signal_const_assert

::godot_rust_script::register_script_class!(
#script_type_ident,
#base_class,
Expand Down Expand Up @@ -433,22 +437,67 @@ fn get_field_description(field: &FieldOpts) -> Option<TokenStream> {
})
}

fn derive_signal_metadata(field: &SpannedValue<FieldOpts>) -> TokenStream {
fn derive_signal_metadata(field: &SpannedValue<FieldOpts>) -> (TokenStream, TokenStream) {
let signal_name = field
.ident
.as_ref()
.map(|ident| ident.to_string())
.unwrap_or_default();
let signal_description = get_field_description(field);
let signal_type = &field.ty;
let signal_ops = match field
.attrs
.iter()
.find(|attr| attr.path().is_ident("signal"))
.and_then(|attr| match &attr.meta {
syn::Meta::Path(_) => None,
syn::Meta::List(_) => Some(FieldSignalOps::from_meta(&attr.meta)),
syn::Meta::NameValue(_) => Some(Err(darling::Error::custom(
"Signal attribute does not support assigning a value!",
)
.with_span(&attr.meta))),
})
.transpose()
{
Ok(ops) => ops,
Err(err) => return (TokenStream::default(), err.write_errors()),
};

quote! {
let const_assert = signal_ops.as_ref().map(|ops| {
let count = ops.0.parsed.len();

quote_spanned! { ops.0.original.span() =>
const _: () = {
assert!(<#signal_type>::ARG_COUNT == #count as u8, "argument names do not match number of arguments.");
};
}
});

let argument_names = signal_ops
.map(|names| {
let span = names.0.original.span();
#[expect(unstable_name_collisions)]
let names: TokenStream = names
.0
.parsed
.iter()
.map(|name| name.to_token_stream())
.intersperse(quote!(,).into_token_stream())
.collect();

quote_spanned! { span => Some(&[#names]) }
})
.unwrap_or_else(|| quote!(None));

let metadata = quote! {
::godot_rust_script::private_export::RustScriptSignalDesc {
name: #signal_name,
arguments: <#signal_type as ::godot_rust_script::ScriptSignal>::argument_desc(),
arguments: <#signal_type>::argument_desc(#argument_names),
description: concat!(#signal_description),
},
}
};

(metadata, const_assert.unwrap_or_default())
}

#[proc_macro_attribute]
Expand Down
1 change: 1 addition & 0 deletions rust-script/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use godot::prelude::{ConvertError, Gd, Object, StringName, Variant};
pub use crate::runtime::Context;

pub use export::GodotScriptExport;
#[expect(deprecated)]
pub use signals::{ScriptSignal, Signal};

pub trait GodotScript: Debug + GodotScriptImpl<ImplBase = Self::Base> {
Expand Down
119 changes: 73 additions & 46 deletions rust-script/src/interface/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,32 @@
use std::marker::PhantomData;

use godot::builtin::{
Callable, Dictionary, GString, NodePath, StringName, Variant, Vector2, Vector3,
Callable, Dictionary, GString, NodePath, StringName, Variant, Vector2, Vector3, Vector4,
};
use godot::classes::Object;
use godot::global::{Error, PropertyHint};
use godot::meta::{GodotConvert, GodotType, ToGodot};
use godot::obj::Gd;
use godot::obj::{Gd, GodotClass};

use crate::static_script_registry::RustScriptPropDesc;

pub trait ScriptSignal {
type Args: SignalArguments;

fn new(host: Gd<Object>, name: &'static str) -> Self;

fn emit(&self, args: Self::Args);

fn connect(&mut self, callable: Callable) -> Result<(), Error>;

fn argument_desc() -> Box<[RustScriptPropDesc]>;

fn name(&self) -> &str;
}
use crate::{GodotScript, RsRef};

pub trait SignalArguments {
fn count() -> u8;
const COUNT: u8;

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

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

impl SignalArguments for () {
fn count() -> u8 {
0
}
const COUNT: u8 = 0;

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

fn argument_desc() -> Box<[RustScriptPropDesc]> {
fn argument_desc(_arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
Box::new([])
}
}
Expand All @@ -60,9 +45,7 @@ macro_rules! count_tts {
macro_rules! tuple_args {
(impl $($arg: ident),+) => {
impl<$($arg: ToGodot),+> SignalArguments for ($($arg,)+) {
fn count() -> u8 {
count_tts!($($arg)+)
}
const COUNT: u8 = count_tts!($($arg)+);

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

fn argument_desc() -> Box<[RustScriptPropDesc]> {
fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
#[expect(non_snake_case)]
let [$($arg),+] = arg_names.unwrap_or(&[$(stringify!($arg)),+]).try_into().unwrap(); //.unwrap_or_else(|| [$(stringify!($arg)),+]);

Box::new([
$(signal_argument_desc!("0", $arg)),+
$(signal_argument_desc!($arg, $arg)),+
])
}
}
Expand All @@ -98,17 +84,17 @@ macro_rules! tuple_args {
macro_rules! single_args {
(impl $arg: ty) => {
impl SignalArguments for $arg {
fn count() -> u8 {
1
}
const COUNT: u8 = 1;

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

fn argument_desc() -> Box<[RustScriptPropDesc]> {
fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
let [arg_name] = arg_names.unwrap_or_else(|| &["0"]).try_into().unwrap();

Box::new([
signal_argument_desc!("0", $arg),
signal_argument_desc!(arg_name, $arg),
])
}
}
Expand All @@ -120,7 +106,7 @@ macro_rules! single_args {
}

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

tuple_args!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10);
single_args!(
bool, u8, u16, u32, u64, i8, i16, i32, i64, f64, GString, StringName, NodePath, Vector2,
Vector3, Dictionary
bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, GString, StringName, NodePath, Vector2,
Vector3, Vector4, Dictionary
);

impl<T: GodotClass> SignalArguments for Gd<T> {
const COUNT: u8 = 1;

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

fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
let name = arg_names
.and_then(|list| list.first())
.copied()
.unwrap_or("0");

Box::new([signal_argument_desc!(name, Self)])
}
}

impl<T: GodotScript> SignalArguments for RsRef<T> {
const COUNT: u8 = 1;

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

fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
Box::new([signal_argument_desc!(
arg_names
.and_then(|list| list.first())
.copied()
.unwrap_or("0"),
Self
)])
}
}

#[derive(Debug)]
pub struct Signal<T: SignalArguments> {
pub struct ScriptSignal<T: SignalArguments> {
host: Gd<Object>,
name: &'static str,
args: PhantomData<T>,
}

impl<T: SignalArguments> ScriptSignal for Signal<T> {
type Args = T;
#[deprecated(
note = "The Signal type has been deprecated and will be removed soon. Please use the ScriptSignal instead."
)]
pub type Signal<T> = ScriptSignal<T>;

impl<T: SignalArguments> ScriptSignal<T> {
pub const ARG_COUNT: u8 = T::COUNT;

fn new(host: Gd<Object>, name: &'static str) -> Self {
pub fn new(host: Gd<Object>, name: &'static str) -> Self {
Self {
host,
name,
args: PhantomData,
}
}

fn emit(&self, args: Self::Args) {
pub fn emit(&self, args: T) {
self.host
.clone()
.emit_signal(self.name, &args.to_variants());
}

fn connect(&mut self, callable: Callable) -> Result<(), Error> {
pub fn connect(&mut self, callable: Callable) -> Result<(), Error> {
match self.host.connect(self.name, &callable) {
Error::OK => Ok(()),
error => Err(error),
}
}

fn argument_desc() -> Box<[RustScriptPropDesc]> {
<T as SignalArguments>::argument_desc()
#[doc(hidden)]
pub fn argument_desc(arg_names: Option<&[&'static str]>) -> Box<[RustScriptPropDesc]> {
<T as SignalArguments>::argument_desc(arg_names)
}

fn name(&self) -> &str {
pub fn name(&self) -> &str {
self.name
}
}

impl<T: SignalArguments> GodotConvert for Signal<T> {
impl<T: SignalArguments> GodotConvert for ScriptSignal<T> {
type Via = godot::builtin::Signal;
}

impl<T: SignalArguments> ToGodot for Signal<T> {
impl<T: SignalArguments> ToGodot for ScriptSignal<T> {
type ToVia<'v>
= Self::Via
where
Expand Down
Loading
Loading