Skip to content

Commit 3a1d77a

Browse files
committed
Type-safe notifications can now be passed to issue_notification() and received in on_notification()
1 parent 9004e71 commit 3a1d77a

File tree

6 files changed

+105
-35
lines changed

6 files changed

+105
-35
lines changed

godot-codegen/src/class_generator.rs

Lines changed: 83 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ pub(crate) fn generate_class_files(
5252
modules.push(GeneratedClassModule {
5353
class_name,
5454
module_name,
55+
own_notification_enum_name: generated_class
56+
.has_own_notification_enum
57+
.then_some(generated_class.notification_enum_name),
5558
inherits_macro_ident: generated_class.inherits_macro_ident,
5659
is_pub: generated_class.has_pub_module,
5760
});
@@ -176,8 +179,16 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
176179
let constants = make_constants(&class.constants, class_name, ctx);
177180
let inherits_macro = format_ident!("inherits_transitive_{}", class_name.rust_ty);
178181
let all_bases = ctx.inheritance_tree().collect_all_bases(class_name);
179-
let virtual_trait = make_virtual_methods_trait(class, &all_bases, &virtual_trait_str, ctx);
180-
let notification_enum = make_notification_enum(class_name, &all_bases, ctx);
182+
let (notification_enum, notification_enum_name) =
183+
make_notification_enum(class_name, &all_bases, ctx);
184+
let virtual_trait = make_virtual_methods_trait(
185+
class,
186+
&all_bases,
187+
&virtual_trait_str,
188+
&notification_enum_name,
189+
ctx,
190+
);
191+
let notify_method = make_notify_method(class_name, ctx);
181192

182193
let memory = if class_name.rust_ty == "Object" {
183194
ident("DynamicRefCount")
@@ -191,6 +202,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
191202
let tokens = quote! {
192203
use godot_ffi as sys;
193204
use crate::engine::*;
205+
use crate::engine::notify::*;
194206
use crate::builtin::*;
195207
use crate::obj::{AsArg, Gd};
196208
use sys::GodotFfi as _;
@@ -207,6 +219,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
207219
#notification_enum
208220
impl #class_name {
209221
#constructor
222+
#notify_method
210223
#methods
211224
#constants
212225
}
@@ -261,19 +274,43 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
261274

262275
GeneratedClass {
263276
tokens,
277+
notification_enum_name,
278+
has_own_notification_enum: notification_enum.is_some(),
264279
inherits_macro_ident: inherits_macro,
265280
has_pub_module: !enums.is_empty(),
266281
}
267282
}
268283

284+
fn make_notify_method(class_name: &TyName, ctx: &mut Context) -> TokenStream {
285+
let enum_name = ctx.notification_enum_name(class_name);
286+
287+
quote! {
288+
/// Sends the `notification` to all classes inherited by the object, triggering calls to `on_notification()`.
289+
///
290+
/// Starts from the highest ancestor (the `Object` class) and goes down the hierarchy.
291+
///
292+
/// See [docs for `Object::notification()`](https://docs.godotengine.org/en/latest/classes/class_object.html#id3) in Godot.
293+
pub fn issue_notification(&mut self, what: #enum_name) {
294+
self.notification(i32::from(what) as i64, false);
295+
}
296+
297+
/// Like [`Self::issue_notification()`], but starts at the most-derived class and goes up the hierarchy.
298+
///
299+
/// See [docs for `Object::notification()`](https://docs.godotengine.org/en/latest/classes/class_object.html#id3) in Godot.
300+
pub fn issue_notification_reversed(&mut self, what: #enum_name) {
301+
self.notification(i32::from(what) as i64, true);
302+
}
303+
}
304+
}
305+
269306
fn make_notification_enum(
270307
class_name: &TyName,
271308
all_bases: &Vec<TyName>,
272309
ctx: &mut Context,
273-
) -> TokenStream {
310+
) -> (Option<TokenStream>, Ident) {
274311
let Some(all_constants) = ctx.notification_constants(class_name) else {
275312
// Class has no notification constants: reuse (direct/indirect) base enum
276-
return TokenStream::new();
313+
return (None, ctx.notification_enum_name(class_name));
277314
};
278315

279316
// Collect all notification constants from current and base classes
@@ -287,6 +324,10 @@ fn make_notification_enum(
287324
workaround_constant_collision(&mut all_constants);
288325

289326
let enum_name = ctx.notification_enum_name(class_name);
327+
let doc_str = format!(
328+
"Notification type for class [`{c}`][crate::engine::{c}].",
329+
c = class_name.rust_ty
330+
);
290331

291332
let mut notification_enumerators_pascal = Vec::new();
292333
let mut notification_enumerators_ord = Vec::new();
@@ -295,8 +336,8 @@ fn make_notification_enum(
295336
notification_enumerators_ord.push(constant_value);
296337
}
297338

298-
quote! {
299-
/// Notification type for class `#class_name`.
339+
let code = quote! {
340+
#[doc = #doc_str]
300341
///
301342
/// Makes it easier to keep an overview all possible notification variants for a given class, including
302343
/// notifications defined in base classes.
@@ -312,27 +353,31 @@ fn make_notification_enum(
312353
Unknown(i32),
313354
}
314355

315-
impl #enum_name {
356+
impl From<i32> for #enum_name {
316357
/// Always succeeds, mapping unknown integers to the `Unknown` variant.
317-
pub fn from_i32(enumerator: i32) -> Self {
358+
fn from(enumerator: i32) -> Self {
318359
match enumerator {
319360
#(
320361
#notification_enumerators_ord => Self::#notification_enumerators_pascal,
321362
)*
322363
other_int => Self::Unknown(other_int),
323364
}
324365
}
366+
}
325367

326-
pub fn to_i32(self) -> i32 {
327-
match self {
368+
impl From<#enum_name> for i32 {
369+
fn from(notification: #enum_name) -> i32 {
370+
match notification {
328371
#(
329-
Self::#notification_enumerators_pascal => #notification_enumerators_ord,
372+
#enum_name::#notification_enumerators_pascal => #notification_enumerators_ord,
330373
)*
331-
Self::Unknown(int) => int,
374+
#enum_name::Unknown(int) => int,
332375
}
333376
}
334377
}
335-
}
378+
};
379+
380+
(Some(code), enum_name)
336381
}
337382

338383
/// Workaround for Godot bug https://github.com/godotengine/godot/issues/75839
@@ -408,23 +453,36 @@ fn make_builtin_class(
408453
}
409454

410455
fn make_module_file(classes_and_modules: Vec<GeneratedClassModule>) -> TokenStream {
411-
let decls = classes_and_modules.iter().map(|m| {
456+
let mut class_decls = Vec::new();
457+
let mut notify_decls = Vec::new();
458+
459+
for m in classes_and_modules.iter() {
412460
let GeneratedClassModule {
413461
module_name,
414462
class_name,
463+
own_notification_enum_name,
415464
is_pub,
416465
..
417466
} = m;
418467
let virtual_trait_name = ident(&class_name.virtual_trait_name());
419468

420469
let vis = is_pub.then_some(quote! { pub });
421470

422-
quote! {
471+
let class_decl = quote! {
423472
#vis mod #module_name;
424473
pub use #module_name::re_export::#class_name;
425474
pub use #module_name::re_export::#virtual_trait_name;
475+
};
476+
class_decls.push(class_decl);
477+
478+
if let Some(enum_name) = own_notification_enum_name {
479+
let notify_decl = quote! {
480+
pub use super::#module_name::re_export::#enum_name;
481+
};
482+
483+
notify_decls.push(notify_decl);
426484
}
427-
});
485+
}
428486

429487
let macros = classes_and_modules.iter().map(|m| {
430488
let GeneratedClassModule {
@@ -440,7 +498,11 @@ fn make_module_file(classes_and_modules: Vec<GeneratedClassModule>) -> TokenStre
440498
});
441499

442500
quote! {
443-
#( #decls )*
501+
#( #class_decls )*
502+
503+
pub mod notify {
504+
#( #notify_decls )*
505+
}
444506

445507
#[doc(hidden)]
446508
pub mod class_macros {
@@ -1076,12 +1138,13 @@ fn make_virtual_methods_trait(
10761138
class: &Class,
10771139
all_bases: &[TyName],
10781140
trait_name: &str,
1141+
notification_enum_name: &Ident,
10791142
ctx: &mut Context,
10801143
) -> TokenStream {
10811144
let trait_name = ident(trait_name);
10821145

10831146
let virtual_method_fns = make_all_virtual_methods(class, all_bases, ctx);
1084-
let special_virtual_methods = special_virtual_methods();
1147+
let special_virtual_methods = special_virtual_methods(notification_enum_name);
10851148

10861149
quote! {
10871150
#[allow(unused_variables)]
@@ -1093,7 +1156,7 @@ fn make_virtual_methods_trait(
10931156
}
10941157
}
10951158

1096-
fn special_virtual_methods() -> TokenStream {
1159+
fn special_virtual_methods(notification_enum_name: &Ident) -> TokenStream {
10971160
quote! {
10981161
fn register_class(builder: &mut crate::builder::ClassBuilder<Self>) {
10991162
unimplemented!()
@@ -1126,7 +1189,7 @@ fn special_virtual_methods() -> TokenStream {
11261189
/// See also in Godot docs:
11271190
/// * [`Object::_notification`](https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-notification).
11281191
/// * [Godot notifications](https://docs.godotengine.org/en/stable/tutorials/best_practices/godot_notifications.html).
1129-
fn on_notification(&mut self, what: i32) {
1192+
fn on_notification(&mut self, what: #notification_enum_name) {
11301193
unimplemented!()
11311194
}
11321195
}

godot-codegen/src/context.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl<'a> Context<'a> {
5959
let mut has_notifications = false;
6060

6161
for constant in constants.iter() {
62-
if let Some(rust_constant) = util::try_to_notification(&constant) {
62+
if let Some(rust_constant) = util::try_to_notification(constant) {
6363
// First time
6464
if !has_notifications {
6565
ctx.notifications_by_class
@@ -94,10 +94,8 @@ impl<'a> Context<'a> {
9494
let all_bases = ctx.inheritance_tree.collect_all_bases(class_name);
9595

9696
let mut nearest = None;
97-
for i in 0..all_bases.len() {
98-
if let Some(nearest_enum_name) =
99-
ctx.notification_enum_names_by_class.get(&all_bases[i])
100-
{
97+
for (i, elem) in all_bases.iter().enumerate() {
98+
if let Some(nearest_enum_name) = ctx.notification_enum_names_by_class.get(elem) {
10199
nearest = Some((i, nearest_enum_name.clone()));
102100
break;
103101
}
@@ -111,6 +109,10 @@ impl<'a> Context<'a> {
111109
ctx.notification_enum_names_by_class
112110
.insert(base_name.clone(), nearest_enum_name.clone());
113111
}
112+
113+
// Also for this class, reuse the type name.
114+
ctx.notification_enum_names_by_class
115+
.insert(class_name.clone(), nearest_enum_name.clone());
114116
}
115117

116118
ctx
@@ -150,7 +152,7 @@ impl<'a> Context<'a> {
150152
pub fn notification_enum_name(&self, class_name: &TyName) -> Ident {
151153
self.notification_enum_names_by_class
152154
.get(class_name)
153-
.expect("every class must have a notification enum name")
155+
.unwrap_or_else(|| panic!("class {} has no notification enum name", class_name.rust_ty))
154156
.clone()
155157
}
156158

godot-codegen/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ impl ToTokens for ModName {
227227

228228
struct GeneratedClass {
229229
tokens: TokenStream,
230+
notification_enum_name: Ident,
231+
has_own_notification_enum: bool,
230232
inherits_macro_ident: Ident,
231233
has_pub_module: bool,
232234
}
@@ -238,6 +240,7 @@ struct GeneratedBuiltin {
238240
struct GeneratedClassModule {
239241
class_name: TyName,
240242
module_name: ModName,
243+
own_notification_enum_name: Option<Ident>,
241244
inherits_macro_ident: Ident,
242245
is_pub: bool,
243246
}

godot-codegen/src/special_cases.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub(crate) fn is_private(class_name: &TyName, godot_method_name: &str) -> bool {
5454
| ("RefCounted", "init_ref")
5555
| ("RefCounted", "reference")
5656
| ("RefCounted", "unreference")
57+
| ("Object", "notification")
5758

5859
=> true, _ => false
5960
}

godot-macros/src/godot_api.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
280280
on_notification_impl = quote! {
281281
impl ::godot::obj::cap::GodotNotification for #class_name {
282282
fn __godot_notification(&mut self, what: i32) {
283-
<Self as #trait_name>::on_notification(self, what)
283+
<Self as #trait_name>::on_notification(self, what.into())
284284
}
285285
}
286286
};

itest/rust/src/virtual_methods_test.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::TestContext;
1010
use godot::bind::{godot_api, GodotClass};
1111
use godot::builtin::GodotString;
1212
use godot::engine::node::InternalMode;
13+
use godot::engine::notify::NodeNotification;
1314
use godot::engine::{Node, Node2D, Node2DVirtual, NodeVirtual, RefCounted, RefCountedVirtual};
1415
use godot::obj::{Base, Gd, Share};
1516
use godot::prelude::PackedStringArray;
@@ -128,12 +129,12 @@ struct NotificationTest {
128129
#[base]
129130
base: Base<Node>,
130131

131-
sequence: Vec<i32>,
132+
sequence: Vec<NodeNotification>,
132133
}
133134

134135
#[godot_api]
135136
impl NodeVirtual for NotificationTest {
136-
fn on_notification(&mut self, what: i32) {
137+
fn on_notification(&mut self, what: NodeNotification) {
137138
self.sequence.push(what);
138139
}
139140

@@ -281,16 +282,16 @@ fn test_notifications() {
281282
let obj = Gd::<NotificationTest>::new_default();
282283

283284
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);
285+
node.issue_notification(NodeNotification::Unpaused);
286+
node.issue_notification(NodeNotification::EditorPostSave);
287+
node.issue_notification(NodeNotification::WmSizeChanged);
287288

288289
assert_eq!(
289290
obj.bind().sequence,
290291
vec![
291-
Node::NOTIFICATION_UNPAUSED,
292-
Node::NOTIFICATION_EDITOR_POST_SAVE,
293-
Node::NOTIFICATION_WM_SIZE_CHANGED,
292+
Node::NOTIFICATION_UNPAUSED.into(),
293+
Node::NOTIFICATION_EDITOR_POST_SAVE.into(),
294+
Node::NOTIFICATION_WM_SIZE_CHANGED.into(),
294295
]
295296
);
296297

0 commit comments

Comments
 (0)