Skip to content

Commit

Permalink
Add SignalBuilder struct; rename SignalArgument -> SignalParam; integ…
Browse files Browse the repository at this point in the history
…rate into ClassBuilder
  • Loading branch information
Bromeon committed Dec 19, 2021
1 parent 1c94e10 commit fb26afa
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 75 deletions.
5 changes: 1 addition & 4 deletions examples/dodge_the_creeps/src/hud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ pub struct Hud;
#[methods]
impl Hud {
fn register_hud(builder: &ClassBuilder<Self>) {
builder.add_signal(Signal {
name: "start_game",
args: &[],
});
builder.signal("start_game").done();
}

fn new(_owner: &CanvasLayer) -> Self {
Expand Down
5 changes: 1 addition & 4 deletions examples/dodge_the_creeps/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ pub struct Player {
#[methods]
impl Player {
fn register_player(builder: &ClassBuilder<Self>) {
builder.add_signal(Signal {
name: "hit",
args: &[],
});
builder.signal("hit").done()
}

fn new(_owner: &Area2D) -> Self {
Expand Down
18 changes: 5 additions & 13 deletions examples/signals/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,13 @@ struct SignalEmitter {
#[methods]
impl SignalEmitter {
fn register_signals(builder: &ClassBuilder<Self>) {
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",
default: Variant::new(100),
export_info: ExportInfo::new(VariantType::I64),
usage: PropertyUsage::DEFAULT,
}],
});
.param_default("data", Variant::new(100))
.done();
}

fn new(_owner: &Node) -> Self {
Expand Down
22 changes: 5 additions & 17 deletions gdnative-async/src/rt/func_state.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use gdnative_bindings::Reference;
use gdnative_core::core_types::{ToVariant, Variant, VariantType};
use gdnative_core::core_types::{ToVariant, Variant};
use gdnative_core::export::user_data::{LocalCellData, Map, MapMut};
use gdnative_core::export::{
ClassBuilder, ExportInfo, NativeClass, NativeClassMethods, PropertyUsage, Signal,
SignalArgument, StaticArgs, StaticArgsMethod,
ClassBuilder, NativeClass, NativeClassMethods, StaticArgs, StaticArgsMethod,
};
use gdnative_core::godot_site;
use gdnative_core::object::ownership::Unique;
Expand Down Expand Up @@ -32,20 +31,9 @@ impl NativeClass for FuncState {
}

fn register_properties(builder: &ClassBuilder<Self>) {
builder.add_signal(Signal {
name: "completed",
args: &[SignalArgument {
name: "value",
default: Variant::nil(),
export_info: ExportInfo::new(VariantType::Nil),
usage: PropertyUsage::DEFAULT,
}],
});

builder.add_signal(Signal {
name: "resumable",
args: &[],
});
builder.signal("completed").param("value").done();

builder.signal("resumable").done();
}
}

Expand Down
80 changes: 56 additions & 24 deletions gdnative-core/src/export/class_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
//! For full examples, see [`examples`](https://github.com/godot-rust/godot-rust/tree/master/examples)
//! in the godot-rust repository.

use crate::core_types::GodotString;
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;
Expand Down Expand Up @@ -141,37 +141,81 @@ impl<C: NativeClass> ClassBuilder<C> {
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<MyType>) {
/// // Add signal without parameters
/// builder
/// .signal("jumped")
/// .done();
///
/// // Add another signal with 1 parameter (untyped)
/// builder
/// .signal("fired")
/// .param("weapon_type")
/// .done();
///
/// // Add third signal with int + String parameters, the latter with a default value "Kerosene"
/// builder
/// .signal("used_jetpack")
/// .param_typed("fuel_spent", VariantType::I64)
/// .param_default("fuel_type", Variant::new("Kerosene"))
/// .done();
/// }
/// }
/// ```
#[inline]
pub fn add_signal(&self, signal: Signal) {
pub fn signal(&self, name: impl Into<GodotString>) -> SignalBuilder<C> {
SignalBuilder::new(self, name.into())
}

#[inline]
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::<Vec<_>>();
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(),
usage: arg.usage.to_sys(),
default_value: arg.default.to_sys(),
})
.collect::<Vec<_>>();

(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(),
},
Expand Down Expand Up @@ -211,15 +255,3 @@ impl<C: NativeClass> ClassBuilder<C> {
}
}
}

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,
}
2 changes: 1 addition & 1 deletion gdnative-core/src/export/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<C>,
class_builder: &'a ClassBuilder<C>,
name: &'a str,
method: F,

Expand Down
2 changes: 2 additions & 0 deletions gdnative-core/src/export/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod class_builder;
mod macros;
mod method;
mod property;
mod signal;

pub(crate) mod class_registry;
pub(crate) mod emplace;
Expand All @@ -26,3 +27,4 @@ pub use class::*;
pub use class_builder::*;
pub use method::*;
pub use property::*;
pub use signal::*;
2 changes: 2 additions & 0 deletions gdnative-core/src/export/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NS>(
self,
Expand Down
114 changes: 114 additions & 0 deletions gdnative-core/src/export/signal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use crate::core_types::{GodotString, Variant, VariantType};
use crate::export::{ClassBuilder, ExportInfo, NativeClass, PropertyUsage};

/// Class to construct a signal. Make sure to call [`Self::done()`] in the end.
///
/// Signal parameters can be added with the various `param*()` methods.
/// Keep in mind that unlike function parameters, signal parameters (both their lengths and types)
/// are not statically checked in Godot. The parameter signature you specify is simply to assist you
/// in the editor UI and with auto-generation of GDScript signal handlers.
pub struct SignalBuilder<'a, C> {
class_builder: &'a ClassBuilder<C>,
name: GodotString,
args: Vec<SignalParam>,
}

impl<'a, C: NativeClass> SignalBuilder<'a, C> {
pub(super) fn new(class_builder: &'a ClassBuilder<C>, signal_name: GodotString) -> Self {
Self {
class_builder,
name: signal_name,
args: vec![],
}
}

/// Add a (untyped) parameter for the signal with a name.
#[inline]
pub fn param(self, parameter_name: impl Into<GodotString>) -> Self {
// Note: the use of 'Nil' to express "untyped" is not following official documentation and could be improved.

self.param_custom(SignalParam {
name: parameter_name.into(),
default: Variant::nil(),
export_info: ExportInfo::new(VariantType::Nil),
usage: PropertyUsage::DEFAULT,
})
}

/// Add a parameter for the signal with a name and default value.
///
/// The type is inferred from the default value.
/// Note that GDScript signal parameters are generally untyped and not checked at runtime.
/// The type is solely used for UI purposes.
#[inline]
pub fn param_default(
self,
parameter_name: impl Into<GodotString>,
default_value: Variant,
) -> Self {
let variant_type = default_value.get_type();

self.param_custom(SignalParam {
name: parameter_name.into(),
default: default_value,
export_info: ExportInfo::new(variant_type),
usage: PropertyUsage::DEFAULT,
})
}

/// Add a parameter for the signal with a name and type.
///
/// Note that GDScript signal parameters are generally untyped and not checked at runtime.
/// The type is solely used for UI purposes.
#[inline]
pub fn param_typed(
self,
parameter_name: impl Into<GodotString>,
parameter_type: VariantType,
) -> Self {
self.param_custom(SignalParam {
name: parameter_name.into(),
default: Variant::nil(),
export_info: ExportInfo::new(parameter_type),
usage: PropertyUsage::DEFAULT,
})
}

/// Add a parameter for the signal, manually configured.
#[inline]
pub fn param_custom(mut self, parameter: SignalParam) -> Self {
self.args.push(parameter);
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<SignalParam>,
}

/// Parameter in a signal declaration.
///
/// Instead of providing values for each field, check out the `param*()` methods in [`SignalBuilder`].
pub struct SignalParam {
/// Parameter name.
pub name: GodotString,

/// Default value, used when no argument is provided.
pub default: Variant,

/// Metadata and UI hints about exporting, e.g. parameter type.
pub export_info: ExportInfo,

/// In which context the signal parameter is used.
pub usage: PropertyUsage,
}
4 changes: 2 additions & 2 deletions gdnative-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ pub fn profiled(meta: TokenStream, input: TokenStream) -> TokenStream {
/// Self {}
/// }
/// fn my_register_function(builder: &ClassBuilder<Foo>) {
/// builder.add_signal(Signal { name: "foo", args: &[] });
/// builder.property::<f32>("bar")
/// builder.signal("my_sig").done();
/// builder.property::<f32>("my_prop")
/// .with_getter(|_, _| 42.0)
/// .with_hint(FloatHint::Range(RangeHint::new(0.0, 100.0)))
/// .done();
Expand Down
2 changes: 1 addition & 1 deletion gdnative/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, SignalBuilder, SignalParam,
};
pub use gdnative_core::init::InitHandle;
pub use gdnative_core::object::{
Expand Down
13 changes: 4 additions & 9 deletions test/src/test_register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,10 @@ impl NativeClass for RegisterSignal {
RegisterSignal
}
fn register_properties(builder: &ClassBuilder<Self>) {
builder.add_signal(Signal {
name: "progress",
args: &[SignalArgument {
name: "amount",
default: Variant::nil(),
export_info: ExportInfo::new(VariantType::I64),
usage: PropertyUsage::DEFAULT,
}],
});
builder
.signal("progress")
.param_typed("amount", VariantType::I64)
.done();
}
}

Expand Down

0 comments on commit fb26afa

Please sign in to comment.