diff --git a/examples/dodge_the_creeps/src/hud.rs b/examples/dodge_the_creeps/src/hud.rs index 8fe05be63..0a8194828 100644 --- a/examples/dodge_the_creeps/src/hud.rs +++ b/examples/dodge_the_creeps/src/hud.rs @@ -9,10 +9,7 @@ pub struct Hud; #[methods] impl Hud { fn register_hud(builder: &ClassBuilder) { - builder.add_signal(Signal { - name: "start_game", - args: &[], - }); + builder.signal("start_game").done(); } fn new(_owner: &CanvasLayer) -> Self { diff --git a/examples/dodge_the_creeps/src/player.rs b/examples/dodge_the_creeps/src/player.rs index e28716924..fdd49614a 100644 --- a/examples/dodge_the_creeps/src/player.rs +++ b/examples/dodge_the_creeps/src/player.rs @@ -16,10 +16,7 @@ pub struct Player { #[methods] impl Player { fn register_player(builder: &ClassBuilder) { - builder.add_signal(Signal { - name: "hit", - args: &[], - }); + builder.signal("hit").done() } fn new(_owner: &Area2D) -> Self { diff --git a/examples/signals/src/lib.rs b/examples/signals/src/lib.rs index eb53250c1..25bfc77c0 100644 --- a/examples/signals/src/lib.rs +++ b/examples/signals/src/lib.rs @@ -12,21 +12,18 @@ struct SignalEmitter { #[methods] impl SignalEmitter { fn register_signals(builder: &ClassBuilder) { - builder.add_signal(Signal { - name: "tick", - args: &[], - }); + builder.signal("tick").done(); - builder.add_signal(Signal { - name: "tick_with_data", + builder + .signal("tick_with_data") // Argument list used by the editor for GUI and generation of GDScript handlers. It can be omitted if the signal is only used from code. - args: &[SignalArgument { - name: "data", + .arg(SignalArgument { + name: "data".into(), default: Variant::new(100), export_info: ExportInfo::new(VariantType::I64), usage: PropertyUsage::DEFAULT, - }], - }); + }) + .done(); } fn new(_owner: &Node) -> Self { diff --git a/gdnative-async/src/rt/func_state.rs b/gdnative-async/src/rt/func_state.rs index 6626f89ea..7f749b8bd 100644 --- a/gdnative-async/src/rt/func_state.rs +++ b/gdnative-async/src/rt/func_state.rs @@ -2,8 +2,8 @@ use gdnative_bindings::Reference; use gdnative_core::core_types::{ToVariant, Variant, VariantType}; use gdnative_core::export::user_data::{LocalCellData, Map, MapMut}; use gdnative_core::export::{ - ClassBuilder, ExportInfo, NativeClass, NativeClassMethods, PropertyUsage, Signal, - SignalArgument, StaticArgs, StaticArgsMethod, + ClassBuilder, ExportInfo, NativeClass, NativeClassMethods, PropertyUsage, SignalArgument, + StaticArgs, StaticArgsMethod, }; use gdnative_core::godot_site; use gdnative_core::object::ownership::Unique; @@ -32,20 +32,17 @@ impl NativeClass for FuncState { } fn register_properties(builder: &ClassBuilder) { - builder.add_signal(Signal { - name: "completed", - args: &[SignalArgument { - name: "value", + builder + .signal("completed") + .arg(SignalArgument { + name: "value".into(), default: Variant::nil(), export_info: ExportInfo::new(VariantType::Nil), usage: PropertyUsage::DEFAULT, - }], - }); + }) + .done(); - builder.add_signal(Signal { - name: "resumable", - args: &[], - }); + builder.signal("resumable").done(); } } diff --git a/gdnative-core/src/export/class_builder.rs b/gdnative-core/src/export/class_builder.rs index fd3a170bf..9a99fe8fc 100644 --- a/gdnative-core/src/export/class_builder.rs +++ b/gdnative-core/src/export/class_builder.rs @@ -34,7 +34,6 @@ use std::ffi::CString; use std::marker::PhantomData; use std::ptr; -use crate::core_types::{GodotString, Variant}; use crate::export::*; use crate::object::NewRef; use crate::private::get_api; @@ -141,23 +140,58 @@ impl ClassBuilder { PropertyBuilder::new(self, name) } + /// Returns a `SignalBuilder` which can be used to add a signal to the class being + /// registered. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use gdnative::prelude::*; + /// + /// #[derive(NativeClass)] + /// #[inherit(Node)] + /// #[register_with(Self::my_register)] + /// #[no_constructor] + /// struct MyType {} + /// + /// // Note: no #[methods] required + /// impl MyType { + /// fn my_register(builder: &ClassBuilder) { + /// builder + /// .signal("sig") + /// .arg(SignalArgument { + /// name: "some_i64".into(), + /// default: Variant::new(100), + /// export_info: ExportInfo::new(VariantType::I64), + /// usage: PropertyUsage::DEFAULT, + /// }) + /// .done(); + /// } + /// } + /// ``` + #[inline] + pub fn signal<'a>(&'a self, name: &'a str) -> SignalBuilder<'a, C> { + SignalBuilder::new(self, name) + } + #[inline] - pub fn add_signal(&self, signal: Signal) { + pub(crate) fn add_signal(&self, signal: Signal) { unsafe { - let name = GodotString::from_str(signal.name); - let owned = signal + let args_and_hints = signal .args .iter() .map(|arg| { - let arg_name = GodotString::from_str(arg.name); let hint_string = arg.export_info.hint_string.new_ref(); - (arg, arg_name, hint_string) + (arg, hint_string) }) .collect::>(); - let mut args = owned + + let mut sys_args = args_and_hints .iter() - .map(|(arg, arg_name, hint_string)| sys::godot_signal_argument { - name: arg_name.to_sys(), + .map(|(arg, hint_string)| sys::godot_signal_argument { + name: arg.name.to_sys(), type_: arg.default.get_type() as i32, hint: arg.export_info.hint_kind, hint_string: hint_string.to_sys(), @@ -165,13 +199,14 @@ impl ClassBuilder { default_value: arg.default.to_sys(), }) .collect::>(); + (get_api().godot_nativescript_register_signal)( self.init_handle, self.class_name.as_ptr(), &sys::godot_signal { - name: name.to_sys(), - num_args: args.len() as i32, - args: args.as_mut_ptr(), + name: signal.name.to_sys(), + num_args: sys_args.len() as i32, + args: sys_args.as_mut_ptr(), num_default_args: 0, default_args: ptr::null_mut(), }, @@ -211,15 +246,3 @@ impl ClassBuilder { } } } - -pub struct Signal<'l> { - pub name: &'l str, - pub args: &'l [SignalArgument<'l>], -} - -pub struct SignalArgument<'l> { - pub name: &'l str, - pub default: Variant, - pub export_info: ExportInfo, - pub usage: PropertyUsage, -} diff --git a/gdnative-core/src/export/method.rs b/gdnative-core/src/export/method.rs index e191e18d6..e622f2f44 100644 --- a/gdnative-core/src/export/method.rs +++ b/gdnative-core/src/export/method.rs @@ -13,7 +13,7 @@ use crate::object::{Ref, TInstance, TRef}; /// Builder type used to register a method on a `NativeClass`. pub struct MethodBuilder<'a, C, F> { - class_builder: &'a super::ClassBuilder, + class_builder: &'a ClassBuilder, name: &'a str, method: F, diff --git a/gdnative-core/src/export/mod.rs b/gdnative-core/src/export/mod.rs index 11821e4f4..bad3f28b7 100644 --- a/gdnative-core/src/export/mod.rs +++ b/gdnative-core/src/export/mod.rs @@ -14,6 +14,7 @@ mod class_builder; mod macros; mod method; mod property; +mod signal; pub(crate) mod class_registry; pub(crate) mod emplace; @@ -26,3 +27,4 @@ pub use class::*; pub use class_builder::*; pub use method::*; pub use property::*; +pub use signal::*; diff --git a/gdnative-core/src/export/property.rs b/gdnative-core/src/export/property.rs index dc5e38117..d344f4933 100644 --- a/gdnative-core/src/export/property.rs +++ b/gdnative-core/src/export/property.rs @@ -161,6 +161,8 @@ where /// Provides a setter function with the signature `fn(&C, owner: C::Base, value: T)` /// where `C` is the `NativeClass` type being registered and `T` is the type of the property. + /// + /// "shr" stands for "shared reference", as opposed to the more common `&mut self`. #[inline] pub fn with_shr_setter( self, diff --git a/gdnative-core/src/export/signal.rs b/gdnative-core/src/export/signal.rs new file mode 100644 index 000000000..940057b3c --- /dev/null +++ b/gdnative-core/src/export/signal.rs @@ -0,0 +1,46 @@ +use crate::core_types::{GodotString, Variant}; +use crate::export::{ClassBuilder, ExportInfo, NativeClass, PropertyUsage}; + +pub struct SignalBuilder<'a, C> { + class_builder: &'a ClassBuilder, + name: GodotString, + args: Vec, +} + +impl<'a, C: NativeClass> SignalBuilder<'a, C> { + pub(super) fn new(class_builder: &'a ClassBuilder, name: &str) -> Self { + Self { + class_builder, + name: GodotString::from(name), + args: vec![], + } + } + + /// Add an argument for the signal. + #[inline] + pub fn arg(mut self, argument: SignalArgument) -> Self { + self.args.push(argument); + self + } + + /// Finish registering the signal. + #[inline] + pub fn done(self) { + self.class_builder.add_signal(Signal { + name: self.name, + args: self.args, + }); + } +} + +pub(crate) struct Signal { + pub name: GodotString, + pub args: Vec, +} + +pub struct SignalArgument { + pub name: GodotString, + pub default: Variant, + pub export_info: ExportInfo, + pub usage: PropertyUsage, +} diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index bd9b70d7f..7a6ed0288 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -176,8 +176,8 @@ pub fn profiled(meta: TokenStream, input: TokenStream) -> TokenStream { /// Self {} /// } /// fn my_register_function(builder: &ClassBuilder) { -/// builder.add_signal(Signal { name: "foo", args: &[] }); -/// builder.property::("bar") +/// builder.signal("my_sig").done(); +/// builder.property::("my_prop") /// .with_getter(|_, _| 42.0) /// .with_hint(FloatHint::Range(RangeHint::new(0.0, 100.0))) /// .done(); diff --git a/gdnative/src/prelude.rs b/gdnative/src/prelude.rs index f9247134f..d02ceea58 100644 --- a/gdnative/src/prelude.rs +++ b/gdnative/src/prelude.rs @@ -17,7 +17,7 @@ pub use gdnative_core::core_types::{ }; pub use gdnative_core::export::{ ClassBuilder, ExportInfo, Method, MethodBuilder, NativeClass, NativeClassMethods, - PropertyUsage, Signal, SignalArgument, + PropertyUsage, SignalArgument, SignalBuilder, }; pub use gdnative_core::init::InitHandle; pub use gdnative_core::object::{ diff --git a/test/src/test_register.rs b/test/src/test_register.rs index d25a4a07c..1b82c5d8b 100644 --- a/test/src/test_register.rs +++ b/test/src/test_register.rs @@ -31,15 +31,15 @@ impl NativeClass for RegisterSignal { RegisterSignal } fn register_properties(builder: &ClassBuilder) { - builder.add_signal(Signal { - name: "progress", - args: &[SignalArgument { - name: "amount", + builder + .signal("progress") + .arg(SignalArgument { + name: "amount".into(), default: Variant::nil(), export_info: ExportInfo::new(VariantType::I64), usage: PropertyUsage::DEFAULT, - }], - }); + }) + .done(); } }