gdnative_core/export/class_builder.rs
1use std::any::TypeId;
2use std::cell::RefCell;
3use std::collections::HashSet;
4use std::ffi::CString;
5use std::marker::PhantomData;
6use std::ptr;
7
8use crate::core_types::{GodotString, VariantType};
9use crate::export::*;
10use crate::object::NewRef;
11use crate::private::get_api;
12
13// TODO(#996): unify string parameters across all buiders
14// Potential candidates:
15// * &str
16// * impl Into<GodotString>
17// * impl Into<Cow<'a, str>>
18
19/// Allows registration of exported properties, methods and signals.
20///
21/// See member functions of this class for usage examples.
22#[derive(Debug)]
23pub struct ClassBuilder<C> {
24 pub(super) init_handle: *mut libc::c_void,
25 pub(super) class_name: CString,
26 mixins: RefCell<HashSet<TypeId, ahash::RandomState>>,
27 _marker: PhantomData<C>,
28}
29
30impl<C: NativeClass> ClassBuilder<C> {
31 pub(crate) fn new(init_handle: *mut libc::c_void, class_name: CString) -> Self {
32 Self {
33 init_handle,
34 class_name,
35 mixins: RefCell::default(),
36 _marker: PhantomData,
37 }
38 }
39
40 /// Returns a `MethodBuilder` which can be used to add a method to the class being
41 /// registered.
42 ///
43 /// # Examples
44 ///
45 /// Basic usage:
46 /// ```
47 /// use gdnative::prelude::*;
48 /// use gdnative::export::{RpcMode, Varargs};
49 ///
50 /// #[derive(NativeClass)]
51 /// #[register_with(Self::my_register)]
52 /// #[no_constructor]
53 /// struct MyType {}
54 ///
55 /// // Note: no #[methods] required
56 /// impl MyType {
57 /// fn my_method(&self) -> i64 { 42 }
58 ///
59 /// fn my_register(builder: &ClassBuilder<MyType>) {
60 /// builder
61 /// .method("my_method", MyMethod)
62 /// .with_rpc_mode(RpcMode::RemoteSync)
63 /// .done();
64 /// }
65 /// }
66 ///
67 /// // Now, wrap the method (this can do anything and does not need to actually call a method)
68 /// struct MyMethod;
69 /// impl Method<MyType> for MyMethod {
70 /// fn call(&self, this: TInstance<'_, MyType>, _args: Varargs<'_>) -> Variant {
71 /// this.map(|obj: &MyType, _| {
72 /// let result = obj.my_method();
73 /// Variant::new(result)
74 /// }).expect("method call succeeds")
75 /// }
76 /// }
77 /// ```
78 ///
79 #[inline]
80 pub fn method<'a, F: Method<C>>(&'a self, name: &'a str, method: F) -> MethodBuilder<'a, C, F> {
81 MethodBuilder::new(self, name, method)
82 }
83
84 /// Returns a `PropertyBuilder` which can be used to add a property to the class being
85 /// registered.
86 ///
87 /// # Examples
88 ///
89 /// Basic usage:
90 ///
91 /// ```
92 /// use gdnative::prelude::*;
93 ///
94 /// #[derive(NativeClass)]
95 /// #[inherit(Node)]
96 /// #[register_with(Self::my_register)]
97 /// #[no_constructor]
98 /// struct MyType {
99 /// foo: i32,
100 /// }
101 ///
102 /// // Note: no #[methods] required
103 /// impl MyType {
104 /// pub fn get_foo(&self, _owner: TRef<Node>) -> i32 { self.foo }
105 /// pub fn set_foo(&mut self, _owner: TRef<Node>, val: i32) { self.foo = val; }
106 ///
107 /// fn my_register(builder: &ClassBuilder<MyType>) {
108 /// builder
109 /// .property("foo")
110 /// .with_default(5)
111 /// .with_hint((-10..=30).into())
112 /// .with_getter(MyType::get_foo)
113 /// .with_setter(MyType::set_foo)
114 /// .done();
115 /// }
116 /// }
117 /// ```
118 #[inline]
119 pub fn property<'a, T>(&'a self, name: &'a str) -> PropertyBuilder<'a, C, T>
120 where
121 T: Export,
122 {
123 PropertyBuilder::new(self, name)
124 }
125
126 /// Returns a `SignalBuilder` which can be used to add a signal to the class being
127 /// registered.
128 ///
129 /// # Examples
130 ///
131 /// Basic usage:
132 ///
133 /// ```
134 /// use gdnative::prelude::*;
135 ///
136 /// #[derive(NativeClass)]
137 /// #[inherit(Node)]
138 /// #[register_with(Self::my_register)]
139 /// #[no_constructor]
140 /// struct MyType {}
141 ///
142 /// // Note: no #[methods] required
143 /// impl MyType {
144 /// fn my_register(builder: &ClassBuilder<MyType>) {
145 /// // Add signal without parameters
146 /// builder
147 /// .signal("jumped")
148 /// .done();
149 ///
150 /// // Add another signal with 1 parameter (untyped)
151 /// builder
152 /// .signal("fired")
153 /// .with_param_untyped("weapon_type")
154 /// .done();
155 ///
156 /// // Add third signal with int + String parameters, the latter with a default value "Kerosene"
157 /// builder
158 /// .signal("used_jetpack")
159 /// .with_param("fuel_spent", VariantType::I64)
160 /// .with_param_default("fuel_type", Variant::new("Kerosene"))
161 /// .done();
162 /// }
163 /// }
164 /// ```
165 #[inline]
166 pub fn signal(&self, name: &str) -> SignalBuilder<C> {
167 SignalBuilder::new(self, GodotString::from(name))
168 }
169
170 #[inline]
171 pub(crate) fn add_signal(&self, signal: Signal) {
172 unsafe {
173 let args_and_hints = signal
174 .args
175 .iter()
176 .map(|arg| {
177 let hint_string = arg.export_info.hint_string.new_ref();
178 (arg, hint_string)
179 })
180 .collect::<Vec<_>>();
181
182 let mut sys_args = args_and_hints
183 .iter()
184 .map(|(param, hint_string)| sys::godot_signal_argument {
185 name: param.name.to_sys(),
186 type_: Self::get_param_type(param) as i32,
187 hint: param.export_info.hint_kind,
188 hint_string: hint_string.to_sys(),
189 usage: param.usage.to_sys(),
190 default_value: param.default.to_sys(),
191 })
192 .collect::<Vec<_>>();
193
194 (get_api().godot_nativescript_register_signal)(
195 self.init_handle,
196 self.class_name.as_ptr(),
197 &sys::godot_signal {
198 name: signal.name.to_sys(),
199 num_args: sys_args.len() as i32,
200 args: sys_args.as_mut_ptr(),
201 num_default_args: 0,
202 default_args: ptr::null_mut(),
203 },
204 );
205 }
206 }
207
208 /// Returns the declared parameter type, or the default value's type, or Nil (in that order)
209 fn get_param_type(arg: &SignalParam) -> VariantType {
210 let export_type = arg.export_info.variant_type;
211 if export_type != VariantType::Nil {
212 export_type
213 } else {
214 arg.default.get_type()
215 }
216 }
217
218 pub(crate) fn add_method(&self, method: ScriptMethod) {
219 let method_name = CString::new(method.name).unwrap();
220
221 let attr = sys::godot_method_attributes {
222 rpc_type: method.attributes.rpc_mode.sys(),
223 };
224
225 let method_desc = sys::godot_instance_method {
226 method: method.method_ptr,
227 method_data: method.method_data,
228 free_func: method.free_func,
229 };
230
231 unsafe {
232 (get_api().godot_nativescript_register_method)(
233 self.init_handle,
234 self.class_name.as_ptr() as *const _,
235 method_name.as_ptr() as *const _,
236 attr,
237 method_desc,
238 );
239 }
240 }
241
242 /// Add a mixin to the class being registered.
243 ///
244 /// # Examples
245 ///
246 /// ```
247 /// use gdnative::prelude::*;
248 ///
249 /// #[derive(NativeClass)]
250 /// #[inherit(Node)]
251 /// #[register_with(my_register)]
252 /// #[no_constructor]
253 /// struct MyType {}
254 ///
255 /// // This creates a opaque type `MyMixin` in the current scope that implements
256 /// // the `Mixin` trait. Mixin types have no public interface or stable layout.
257 /// #[methods(mixin = "MyMixin")]
258 /// impl MyType {
259 /// #[method]
260 /// fn my_method(&self) -> i64 { 42 }
261 /// }
262 ///
263 /// fn my_register(builder: &ClassBuilder<MyType>) {
264 /// builder.mixin::<MyMixin>();
265 /// }
266 /// ```
267 #[inline]
268 pub fn mixin<M: Mixin<C>>(&self) {
269 if self.mixins.borrow_mut().insert(TypeId::of::<M>()) {
270 M::register(self);
271 }
272 }
273}
274
275/// Trait for mixins, manually registered `#[methods]` blocks that may be applied to multiple types.
276///
277/// This trait is implemented on generated types by the `#[methods]` proc-macro only, and has no public interface.
278/// Use [`ClassBuilder::mixin`] to register mixins to [`NativeClass`] types.
279pub trait Mixin<C>: crate::private::mixin::Sealed + 'static
280where
281 C: NativeClass,
282{
283 #[doc(hidden)]
284 fn register(builder: &ClassBuilder<C>);
285}