Skip to content

Commit d60e3e1

Browse files
committed
Support for lifecycle notifications
1 parent 096eccd commit d60e3e1

File tree

6 files changed

+139
-26
lines changed

6 files changed

+139
-26
lines changed

godot-codegen/src/central_generator.rs

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ fn make_sys_code(central_items: &CentralItems) -> String {
136136
#(#opaque_types)*
137137
}
138138

139+
// ----------------------------------------------------------------------------------------------------------------------------------------------
140+
139141
pub struct GlobalMethodTable {
140142
#(#variant_fn_decls)*
141143
}
@@ -148,6 +150,8 @@ fn make_sys_code(central_items: &CentralItems) -> String {
148150
}
149151
}
150152

153+
// ----------------------------------------------------------------------------------------------------------------------------------------------
154+
151155
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
152156
#[repr(i32)]
153157
pub enum VariantType {
@@ -180,6 +184,8 @@ fn make_sys_code(central_items: &CentralItems) -> String {
180184
ffi_methods! { type GDExtensionTypePtr = *mut Self; .. }
181185
}
182186

187+
// ----------------------------------------------------------------------------------------------------------------------------------------------
188+
183189
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
184190
#[repr(i32)]
185191
pub enum VariantOperator {
@@ -262,7 +268,7 @@ fn make_core_code(central_items: &CentralItems) -> String {
262268
}
263269

264270
fn make_central_items(api: &ExtensionApi, build_config: &str, ctx: &mut Context) -> CentralItems {
265-
let mut opaque_types = vec![];
271+
let mut opaque_types = Vec::new();
266272
for class in &api.builtin_class_sizes {
267273
if class.build_configuration == build_config {
268274
for ClassSize { name, size } in &class.sizes {
@@ -327,7 +333,7 @@ fn make_central_items(api: &ExtensionApi, build_config: &str, ctx: &mut Context)
327333

328334
result
329335
.variant_op_enumerators_pascal
330-
.push(ident(&shout_to_pascal(name)));
336+
.push(ident(&util::shout_to_pascal(name)));
331337
result
332338
.variant_op_enumerators_ord
333339
.push(Literal::i32_unsuffixed(op.value));
@@ -703,22 +709,3 @@ fn is_trivial(type_names: &TypeNames) -> bool {
703709

704710
list.contains(&type_names.json_builtin_name.as_str())
705711
}
706-
707-
fn shout_to_pascal(shout_case: &str) -> String {
708-
let mut result = String::with_capacity(shout_case.len());
709-
let mut next_upper = true;
710-
711-
for ch in shout_case.chars() {
712-
if next_upper {
713-
assert_ne!(ch, '_'); // no double underscore
714-
result.push(ch); // unchanged
715-
next_upper = false;
716-
} else if ch == '_' {
717-
next_upper = true;
718-
} else {
719-
result.push(ch.to_ascii_lowercase());
720-
}
721-
}
722-
723-
result
724-
}

godot-codegen/src/class_generator.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,12 +1011,37 @@ fn special_virtual_methods() -> TokenStream {
10111011
fn register_class(builder: &mut crate::builder::ClassBuilder<Self>) {
10121012
unimplemented!()
10131013
}
1014+
1015+
/// Godot constructor, accepting an injected `base` object.
1016+
///
1017+
/// `base` refers to the base instance of the class, which can either be stored in a `#[base]` field or discarded.
1018+
/// This method returns a fully-constructed instance, which will then be moved into a `Gd<T>` pointer.
10141019
fn init(base: crate::obj::Base<Self::Base>) -> Self {
10151020
unimplemented!()
10161021
}
1022+
1023+
/// String representation of the Godot instance.
1024+
///
1025+
/// Override this method to define how the instance is represented as a string.
1026+
/// Used by `str()` and `print()` in GDScript, among others.
10171027
fn to_string(&self) -> crate::builtin::GodotString {
10181028
unimplemented!()
10191029
}
1030+
1031+
/// Called when the object receives a Godot notification.
1032+
///
1033+
/// The type of notification can be identified through `what`, by comparing it with a `NOTIFICATION_*` constant. These constants are
1034+
/// defined across multiple classes, most notably in [`Node`](https://docs.godotengine.org/en/stable/classes/class_node.html#constants).
1035+
///
1036+
/// This method is named `_notification` in Godot, but `on_notification` in Rust, to avoid conflicts with the
1037+
/// [`Object::notification`][crate::engine::Object::notification] method that _issues_ notifications.
1038+
///
1039+
/// See also in Godot docs:
1040+
/// * [`Object::_notification`](https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-notification).
1041+
/// * [Godot notifications](https://docs.godotengine.org/en/stable/tutorials/best_practices/godot_notifications.html).
1042+
fn on_notification(&mut self, what: i32) {
1043+
unimplemented!()
1044+
}
10201045
}
10211046
}
10221047

godot-core/src/obj/traits.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,13 @@ pub mod cap {
156156
fn __godot_to_string(&self) -> GodotString;
157157
}
158158

159+
// TODO Evaluate whether we want this public or not
160+
#[doc(hidden)]
161+
pub trait GodotNotification: GodotClass {
162+
#[doc(hidden)]
163+
fn __godot_notification(&mut self, what: i32);
164+
}
165+
159166
// TODO Evaluate whether we want this public or not
160167
#[doc(hidden)]
161168
pub trait GodotRegisterClass: GodotClass {
@@ -174,7 +181,7 @@ pub mod cap {
174181
fn __register_exports();
175182
}
176183

177-
/// Auto-implemented for `#[godot_api] impl ***Virtual for MyClass` blocks
184+
/// Auto-implemented for `#[godot_api] impl XyVirtual for MyClass` blocks
178185
pub trait ImplementsGodotVirtual: GodotClass {
179186
#[doc(hidden)]
180187
fn __virtual_call(_name: &str) -> sys::GDExtensionClassCallVirtual;

godot-core/src/registry.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,15 @@ pub enum PluginComponent {
8888
unsafe extern "C" fn(
8989
p_instance: sys::GDExtensionClassInstancePtr,
9090
r_is_valid: *mut sys::GDExtensionBool,
91-
p_out: sys::GDExtensionStringPtr,
91+
r_out: sys::GDExtensionStringPtr,
92+
),
93+
>,
94+
95+
/// User-defined `on_notification` function
96+
user_on_notification_fn: Option<
97+
unsafe extern "C" fn(
98+
p_instance: sys::GDExtensionClassInstancePtr, //
99+
p_what: i32,
92100
),
93101
>,
94102

@@ -113,7 +121,11 @@ struct ClassRegistrationInfo {
113121

114122
/// Registers a class with static type information.
115123
pub fn register_class<
116-
T: cap::GodotInit + cap::ImplementsGodotVirtual + cap::GodotToString + cap::GodotRegisterClass,
124+
T: cap::GodotInit
125+
+ cap::ImplementsGodotVirtual
126+
+ cap::GodotToString
127+
+ cap::GodotNotification
128+
+ cap::GodotRegisterClass,
117129
>() {
118130
// TODO: provide overloads with only some trait impls
119131

@@ -122,6 +134,7 @@ pub fn register_class<
122134

123135
let godot_params = sys::GDExtensionClassCreationInfo {
124136
to_string_func: Some(callbacks::to_string::<T>),
137+
notification_func: Some(callbacks::on_notification::<T>),
125138
reference_func: Some(callbacks::reference::<T>),
126139
unreference_func: Some(callbacks::unreference::<T>),
127140
create_instance_func: Some(callbacks::create::<T>),
@@ -203,11 +216,13 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
203216
user_register_fn,
204217
user_create_fn,
205218
user_to_string_fn,
219+
user_on_notification_fn,
206220
get_virtual_fn,
207221
} => {
208222
c.user_register_fn = user_register_fn;
209223
fill_into(&mut c.godot_params.create_instance_func, user_create_fn);
210224
c.godot_params.to_string_func = user_to_string_fn;
225+
c.godot_params.notification_func = user_on_notification_fn;
211226
c.godot_params.get_virtual_func = Some(get_virtual_fn);
212227
}
213228
}
@@ -348,13 +363,23 @@ pub mod callbacks {
348363

349364
let storage = as_storage::<T>(instance);
350365
let instance = storage.get();
351-
let string = <T as cap::GodotToString>::__godot_to_string(&*instance);
366+
let string = T::__godot_to_string(&*instance);
352367

353368
// Transfer ownership to Godot, disable destructor
354369
string.write_string_sys(out_string);
355370
std::mem::forget(string);
356371
}
357372

373+
pub unsafe extern "C" fn on_notification<T: cap::GodotNotification>(
374+
instance: sys::GDExtensionClassInstancePtr,
375+
what: i32,
376+
) {
377+
let storage = as_storage::<T>(instance);
378+
let mut instance = storage.get_mut();
379+
380+
T::__godot_notification(&mut *instance, what);
381+
}
382+
358383
pub unsafe extern "C" fn reference<T: GodotClass>(instance: sys::GDExtensionClassInstancePtr) {
359384
let storage = as_storage::<T>(instance);
360385
storage.on_inc_ref();

godot-macros/src/godot_api.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,12 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
218218
let mut godot_init_impl = TokenStream::new();
219219
let mut to_string_impl = TokenStream::new();
220220
let mut register_class_impl = TokenStream::new();
221+
let mut on_notification_impl = TokenStream::new();
221222

222223
let mut register_fn = quote! { None };
223224
let mut create_fn = quote! { None };
224225
let mut to_string_fn = quote! { None };
226+
let mut on_notification_fn = quote! { None };
225227

226228
let mut virtual_methods = vec![];
227229
let mut virtual_method_names = vec![];
@@ -274,6 +276,20 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
274276
to_string_fn = quote! { Some(#prv::callbacks::to_string::<#class_name>) };
275277
}
276278

279+
"on_notification" => {
280+
on_notification_impl = quote! {
281+
impl ::godot::obj::cap::GodotNotification for #class_name {
282+
fn __godot_notification(&mut self, what: i32) {
283+
<Self as #trait_name>::on_notification(self, what)
284+
}
285+
}
286+
};
287+
288+
on_notification_fn = quote! {
289+
Some(#prv::callbacks::on_notification::<#class_name>)
290+
};
291+
}
292+
277293
// Other virtual methods, like ready, process etc.
278294
_ => {
279295
let method = util::reduce_to_signature(method);
@@ -300,6 +316,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
300316
#original_impl
301317
#godot_init_impl
302318
#to_string_impl
319+
#on_notification_impl
303320
#register_class_impl
304321

305322
impl ::godot::private::You_forgot_the_attribute__godot_api for #class_name {}
@@ -323,6 +340,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
323340
user_register_fn: #register_fn,
324341
user_create_fn: #create_fn,
325342
user_to_string_fn: #to_string_fn,
343+
user_on_notification_fn: #on_notification_fn,
326344
get_virtual_fn: #prv::callbacks::get_virtual::<#class_name>,
327345
},
328346
});

itest/rust/src/virtual_methods_test.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ struct WithoutInit {
2323
some_base: Base<RefCounted>,
2424
}
2525

26+
// ----------------------------------------------------------------------------------------------------------------------------------------------
27+
2628
#[derive(GodotClass, Debug)]
2729
#[class(init, base=RefCounted)]
2830
struct VirtualMethodTest {
@@ -42,6 +44,8 @@ impl RefCountedVirtual for VirtualMethodTest {
4244
}
4345
}
4446

47+
// ----------------------------------------------------------------------------------------------------------------------------------------------
48+
4549
#[derive(GodotClass, Debug)]
4650
#[class(base=Node2D)]
4751
struct ReadyVirtualTest {
@@ -64,6 +68,8 @@ impl Node2DVirtual for ReadyVirtualTest {
6468
}
6569
}
6670

71+
// ----------------------------------------------------------------------------------------------------------------------------------------------
72+
6773
#[derive(GodotClass, Debug)]
6874
#[class(base=Node2D)]
6975
struct TreeVirtualTest {
@@ -92,6 +98,8 @@ impl Node2DVirtual for TreeVirtualTest {
9298
}
9399
}
94100

101+
// ----------------------------------------------------------------------------------------------------------------------------------------------
102+
95103
#[derive(GodotClass, Debug)]
96104
#[class(base=Node)]
97105
struct ReturnVirtualTest {
@@ -114,6 +122,26 @@ impl NodeVirtual for ReturnVirtualTest {
114122

115123
// ----------------------------------------------------------------------------------------------------------------------------------------------
116124

125+
#[derive(GodotClass, Debug)]
126+
#[class(base=Node, init)]
127+
struct NotificationTest {
128+
#[base]
129+
base: Base<Node>,
130+
131+
sequence: Vec<i32>,
132+
}
133+
134+
#[godot_api]
135+
impl NodeVirtual for NotificationTest {
136+
fn on_notification(&mut self, what: i32) {
137+
self.sequence.push(what);
138+
}
139+
140+
fn ready(&mut self) {}
141+
}
142+
143+
// ----------------------------------------------------------------------------------------------------------------------------------------------
144+
117145
#[itest]
118146
fn test_to_string() {
119147
let _obj = Gd::<VirtualMethodTest>::new_default();
@@ -238,10 +266,33 @@ fn test_tree_enters_exits(test_context: &TestContext) {
238266
}
239267

240268
#[itest]
241-
fn test_virtual_method_with_return(_test_context: &TestContext) {
269+
fn test_virtual_method_with_return() {
242270
let obj = Gd::<ReturnVirtualTest>::new_default();
243271
let output = obj.bind().get_configuration_warnings();
272+
244273
assert!(output.contains("Hello".into()));
245274
assert_eq!(output.len(), 1);
275+
276+
obj.free();
277+
}
278+
279+
#[itest]
280+
fn test_notifications() {
281+
let obj = Gd::<NotificationTest>::new_default();
282+
283+
let mut node = obj.share().upcast::<Node>();
284+
node.notification(Node::NOTIFICATION_UNPAUSED as i64, false);
285+
node.notification(Node::NOTIFICATION_EDITOR_POST_SAVE as i64, false);
286+
node.notification(Node::NOTIFICATION_WM_SIZE_CHANGED as i64, true);
287+
288+
assert_eq!(
289+
obj.bind().sequence,
290+
vec![
291+
Node::NOTIFICATION_UNPAUSED,
292+
Node::NOTIFICATION_EDITOR_POST_SAVE,
293+
Node::NOTIFICATION_WM_SIZE_CHANGED,
294+
]
295+
);
296+
246297
obj.free();
247298
}

0 commit comments

Comments
 (0)