Skip to content

Commit 2d59ee5

Browse files
committed
Document generated engine symbols, add intra-link docs
1 parent ee5b0c2 commit 2d59ee5

File tree

6 files changed

+171
-37
lines changed

6 files changed

+171
-37
lines changed

godot-codegen/src/central_generator.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,12 @@ fn make_core_code(central_items: &CentralItems) -> String {
258258
}
259259
}
260260

261+
/// Global enums and constants.
262+
///
263+
/// A list of global-scope enumerated constants.
264+
/// For global built-in functions, check out the [`utilities` module][crate::engine::utilities].
265+
///
266+
/// See also [Godot docs for `@GlobalScope`](https://docs.godotengine.org/en/stable/classes/[email protected]#enumerations).
261267
pub mod global {
262268
use crate::sys;
263269
#( #global_enum_defs )*

godot-codegen/src/class_generator.rs

Lines changed: 141 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub(crate) fn generate_class_files(
4343
}
4444

4545
let generated_class = make_class(class, &class_name, ctx);
46-
let file_contents = generated_class.tokens.to_string();
46+
let file_contents = generated_class.code.to_string();
4747

4848
let out_path = gen_path.join(format!("{}.rs", module_name.rust_mod));
4949
std::fs::write(&out_path, file_contents).expect("failed to write class file");
@@ -56,7 +56,7 @@ pub(crate) fn generate_class_files(
5656
.has_own_notification_enum
5757
.then_some(generated_class.notification_enum_name),
5858
inherits_macro_ident: generated_class.inherits_macro_ident,
59-
is_pub: generated_class.has_pub_module,
59+
is_pub_sidecar: generated_class.has_sidecar_module,
6060
});
6161
}
6262

@@ -94,7 +94,7 @@ pub(crate) fn generate_builtin_class_files(
9494

9595
let generated_class =
9696
make_builtin_class(class, &class_name, &inner_class_name, type_info, ctx);
97-
let file_contents = generated_class.tokens.to_string();
97+
let file_contents = generated_class.code.to_string();
9898

9999
let out_path = gen_path.join(format!("{}.rs", module_name.rust_mod));
100100
std::fs::write(&out_path, file_contents).expect("failed to write class file");
@@ -112,13 +112,95 @@ pub(crate) fn generate_builtin_class_files(
112112
out_files.push(out_path);
113113
}
114114

115+
fn make_class_doc(
116+
class_name: &TyName,
117+
base_ident_opt: Option<Ident>,
118+
has_notification_enum: bool,
119+
has_sidecar_module: bool,
120+
) -> String {
121+
let TyName { rust_ty, godot_ty } = class_name;
122+
123+
let inherits_line = if let Some(base) = base_ident_opt {
124+
format!("Inherits [`{base}`][crate::engine::{base}].")
125+
} else {
126+
"This is the base class for all other classes at the root of the hierarchy. \
127+
Every instance of `Object` can be stored in a [`Gd`][crate::obj::Gd] smart pointer."
128+
.to_string()
129+
};
130+
131+
let notify_line = if has_notification_enum {
132+
format!("* [`{rust_ty}Notification`][crate::engine::notify::{rust_ty}Notification]: notification type\n")
133+
} else {
134+
String::new()
135+
};
136+
137+
let sidecar_line = if has_sidecar_module {
138+
let module_name = ModName::from_godot(&class_name.godot_ty).rust_mod;
139+
format!("* [`{module_name}`][crate::engine::{module_name}]: sidecar module with related enum/flag types\n")
140+
} else {
141+
String::new()
142+
};
143+
144+
let online_link = format!(
145+
"https://docs.godotengine.org/en/stable/classes/class_{}.html",
146+
godot_ty.to_ascii_lowercase()
147+
);
148+
149+
format!(
150+
"Godot class `{godot_ty}.`\n\n\
151+
\
152+
{inherits_line}\n\n\
153+
\
154+
Related symbols:\n\n\
155+
{sidecar_line}\
156+
* [`{rust_ty}Virtual`][crate::engine::{rust_ty}Virtual]: virtual methods\n\
157+
{notify_line}\
158+
\n\n\
159+
See also [Godot docs for `{godot_ty}`]({online_link}).\n\n",
160+
)
161+
}
162+
163+
fn make_virtual_trait_doc(class_name: &TyName) -> String {
164+
let TyName { rust_ty, godot_ty } = class_name;
165+
166+
let online_link = format!(
167+
"https://docs.godotengine.org/en/stable/classes/class_{}.html#methods",
168+
godot_ty.to_ascii_lowercase()
169+
);
170+
171+
format!(
172+
"Virtual methods for class [`{rust_ty}`][crate::engine::{rust_ty}].\
173+
\n\n\
174+
These methods represent constructors (`init`) or callbacks invoked by the engine.\
175+
\n\n\
176+
See also [Godot docs for `{godot_ty}` methods]({online_link}).\n\n"
177+
)
178+
}
179+
180+
fn make_module_doc(class_name: &TyName) -> String {
181+
let TyName { rust_ty, godot_ty } = class_name;
182+
183+
let online_link = format!(
184+
"https://docs.godotengine.org/en/stable/classes/class_{}.html#enumerations",
185+
godot_ty.to_ascii_lowercase()
186+
);
187+
188+
format!(
189+
"Sidecar module for class [`{rust_ty}`][crate::engine::{rust_ty}].\
190+
\n\n\
191+
Defines related flag and enum types. In GDScript, those are nested under the class scope.\
192+
\n\n\
193+
See also [Godot docs for `{godot_ty}` enums]({online_link}).\n\n"
194+
)
195+
}
196+
115197
fn make_constructor(class: &Class, ctx: &Context) -> TokenStream {
116198
let godot_class_name = &class.name;
117199
if ctx.is_singleton(godot_class_name) {
118200
// Note: we cannot return &'static mut Self, as this would be very easy to mutably alias.
119-
// &'static Self would be possible, but we would lose the whole mutability information (even if that
120-
// is best-effort and not strict Rust mutability, it makes the API much more usable).
121-
// As long as the user has multiple Gd smart pointers to the same singletons, only the internal raw pointers.
201+
// &'static Self would be possible, but we would lose the whole mutability information (even if that is best-effort and
202+
// not strict Rust mutability, it makes the API much more usable).
203+
// As long as the user has multiple Gd smart pointers to the same singletons, only the internal raw pointers are aliased.
122204
// See also Deref/DerefMut impl for Gd.
123205
quote! {
124206
pub fn singleton() -> Gd<Self> {
@@ -165,12 +247,12 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
165247
let virtual_trait_str = class_name.virtual_trait_name();
166248

167249
// Idents and tokens
168-
let base = match class.inherits.as_ref() {
250+
let (base_ty, base_ident_opt) = match class.inherits.as_ref() {
169251
Some(base) => {
170252
let base = ident(&to_pascal_case(base));
171-
quote! { crate::engine::#base }
253+
(quote! { crate::engine::#base }, Some(base))
172254
}
173-
None => quote! { () },
255+
None => (quote! { () }, None),
174256
};
175257

176258
let constructor = make_constructor(class, ctx);
@@ -181,8 +263,17 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
181263
let all_bases = ctx.inheritance_tree().collect_all_bases(class_name);
182264
let (notification_enum, notification_enum_name) =
183265
make_notification_enum(class_name, &all_bases, ctx);
266+
let has_sidecar_module = !enums.is_empty();
267+
let class_doc = make_class_doc(
268+
class_name,
269+
base_ident_opt,
270+
notification_enum.is_some(),
271+
has_sidecar_module,
272+
);
273+
let module_doc = make_module_doc(class_name);
184274
let virtual_trait = make_virtual_methods_trait(
185275
class,
276+
class_name,
186277
&all_bases,
187278
&virtual_trait_str,
188279
&notification_enum_name,
@@ -200,6 +291,8 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
200291

201292
// mod re_export needed, because class should not appear inside the file module, and we can't re-export private struct as pub
202293
let tokens = quote! {
294+
#![doc = #module_doc]
295+
203296
use godot_ffi as sys;
204297
use crate::engine::*;
205298
use crate::engine::notify::*;
@@ -210,6 +303,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
210303
pub(super) mod re_export {
211304
use super::*;
212305

306+
#[doc = #class_doc]
213307
#[derive(Debug)]
214308
#[repr(transparent)]
215309
pub struct #class_name {
@@ -224,7 +318,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
224318
#constants
225319
}
226320
impl crate::obj::GodotClass for #class_name {
227-
type Base = #base;
321+
type Base = #base_ty;
228322
type Declarer = crate::obj::dom::EngineDomain;
229323
type Mem = crate::obj::mem::#memory;
230324

@@ -242,7 +336,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
242336
impl crate::obj::Inherits<crate::engine::#all_bases> for #class_name {}
243337
)*
244338
impl std::ops::Deref for #class_name {
245-
type Target = #base;
339+
type Target = #base_ty;
246340

247341
fn deref(&self) -> &Self::Target {
248342
// SAFETY: same assumptions as `impl Deref for Gd<T>`, see there for comments
@@ -273,30 +367,37 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
273367
// note: TypePtr -> ObjectPtr conversion OK?
274368

275369
GeneratedClass {
276-
tokens,
370+
code: tokens,
277371
notification_enum_name,
278372
has_own_notification_enum: notification_enum.is_some(),
279373
inherits_macro_ident: inherits_macro,
280-
has_pub_module: !enums.is_empty(),
374+
has_sidecar_module,
281375
}
282376
}
283377

284378
fn make_notify_method(class_name: &TyName, ctx: &mut Context) -> TokenStream {
285379
let enum_name = ctx.notification_enum_name(class_name);
286380

287381
quote! {
288-
/// Sends the `notification` to all classes inherited by the object, triggering calls to `on_notification()`.
382+
/// ⚠️ Sends a Godot notification to all classes inherited by the object.
383+
///
384+
/// Triggers calls to `on_notification()`, and depending on the notification, also to Godot's lifecycle callbacks such as `ready()`.
289385
///
290386
/// Starts from the highest ancestor (the `Object` class) and goes down the hierarchy.
387+
/// See also [Godot docs for `Object::notification()`](https://docs.godotengine.org/en/latest/classes/class_object.html#id3).
388+
///
389+
/// # Panics
291390
///
292-
/// See [docs for `Object::notification()`](https://docs.godotengine.org/en/latest/classes/class_object.html#id3) in Godot.
391+
/// If you call this method on a user-defined object while holding a `GdRef` or `GdMut` guard on the instance, you will encounter
392+
/// a panic. The reason is that the receiving virtual method `on_notification()` acquires a `GdMut` lock dynamically, which must
393+
/// be exclusive.
293394
pub fn issue_notification(&mut self, what: #enum_name) {
294395
self.notification(i32::from(what) as i64, false);
295396
}
296397

297-
/// Like [`Self::issue_notification()`], but starts at the most-derived class and goes up the hierarchy.
398+
/// ⚠️ Like [`Self::issue_notification()`], but starts at the most-derived class and goes up the hierarchy.
298399
///
299-
/// See [docs for `Object::notification()`](https://docs.godotengine.org/en/latest/classes/class_object.html#id3) in Godot.
400+
/// See docs of that method, including the panics.
300401
pub fn issue_notification_reversed(&mut self, what: #enum_name) {
301402
self.notification(i32::from(what) as i64, true);
302403
}
@@ -449,7 +550,7 @@ fn make_builtin_class(
449550
};
450551
// note: TypePtr -> ObjectPtr conversion OK?
451552

452-
GeneratedBuiltin { tokens }
553+
GeneratedBuiltin { code: tokens }
453554
}
454555

455556
fn make_module_file(classes_and_modules: Vec<GeneratedClassModule>) -> TokenStream {
@@ -461,7 +562,7 @@ fn make_module_file(classes_and_modules: Vec<GeneratedClassModule>) -> TokenStre
461562
module_name,
462563
class_name,
463564
own_notification_enum_name,
464-
is_pub,
565+
is_pub_sidecar: is_pub,
465566
..
466567
} = m;
467568
let virtual_trait_name = ident(&class_name.virtual_trait_name());
@@ -1136,59 +1237,67 @@ fn make_return(
11361237

11371238
fn make_virtual_methods_trait(
11381239
class: &Class,
1139-
all_bases: &[TyName],
1240+
class_name: &TyName,
1241+
all_base_names: &[TyName],
11401242
trait_name: &str,
11411243
notification_enum_name: &Ident,
11421244
ctx: &mut Context,
11431245
) -> TokenStream {
11441246
let trait_name = ident(trait_name);
11451247

1146-
let virtual_method_fns = make_all_virtual_methods(class, all_bases, ctx);
1248+
let virtual_method_fns = make_all_virtual_methods(class, all_base_names, ctx);
11471249
let special_virtual_methods = special_virtual_methods(notification_enum_name);
11481250

1251+
let trait_doc = make_virtual_trait_doc(class_name);
1252+
11491253
quote! {
1254+
#[doc = #trait_doc]
11501255
#[allow(unused_variables)]
11511256
#[allow(clippy::unimplemented)]
1152-
pub trait #trait_name: crate::private::You_forgot_the_attribute__godot_api + crate::obj::GodotClass {
1153-
#( #virtual_method_fns )*
1257+
pub trait #trait_name: crate::obj::GodotClass + crate::private::You_forgot_the_attribute__godot_api {
11541258
#special_virtual_methods
1259+
#( #virtual_method_fns )*
11551260
}
11561261
}
11571262
}
11581263

11591264
fn special_virtual_methods(notification_enum_name: &Ident) -> TokenStream {
11601265
quote! {
1266+
#[doc(hidden)]
11611267
fn register_class(builder: &mut crate::builder::ClassBuilder<Self>) {
11621268
unimplemented!()
11631269
}
11641270

11651271
/// Godot constructor, accepting an injected `base` object.
11661272
///
11671273
/// `base` refers to the base instance of the class, which can either be stored in a `#[base]` field or discarded.
1168-
/// This method returns a fully-constructed instance, which will then be moved into a `Gd<T>` pointer.
1274+
/// This method returns a fully-constructed instance, which will then be moved into a [`Gd<T>`][crate::obj::Gd] pointer.
1275+
///
1276+
/// If the class has a `#[class(init)]` attribute, this method will be auto-generated and must not be overridden.
11691277
fn init(base: crate::obj::Base<Self::Base>) -> Self {
11701278
unimplemented!()
11711279
}
11721280

11731281
/// String representation of the Godot instance.
11741282
///
11751283
/// Override this method to define how the instance is represented as a string.
1176-
/// Used by `str()` and `print()` in GDScript, among others.
1284+
/// Used by `impl Display for Gd<T>`, as well as `str()` and `print()` in GDScript.
11771285
fn to_string(&self) -> crate::builtin::GodotString {
11781286
unimplemented!()
11791287
}
11801288

11811289
/// Called when the object receives a Godot notification.
11821290
///
1183-
/// The type of notification can be identified through `what`, by comparing it with a `NOTIFICATION_*` constant. These constants are
1184-
/// defined across multiple classes, most notably in [`Node`](https://docs.godotengine.org/en/stable/classes/class_node.html#constants).
1291+
/// The type of notification can be identified through `what`. The enum is designed to hold all possible `NOTIFICATION_*`
1292+
/// constants that the current class can handle. However, this is not validated in Godot, so an enum variant `Unknown` exists
1293+
/// to represent integers out of known constants (mistakes or future additions).
11851294
///
1186-
/// This method is named `_notification` in Godot, but `on_notification` in Rust, to avoid conflicts with the
1187-
/// [`Object::notification`][crate::engine::Object::notification] method that _issues_ notifications.
1295+
/// This method is named `_notification` in Godot, but `on_notification` in Rust. To _send_ notifications, use the
1296+
/// [`Object::issue_notification`][crate::engine::Object::issue_notification] method.
11881297
///
11891298
/// See also in Godot docs:
11901299
/// * [`Object::_notification`](https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-notification).
1191-
/// * [Godot notifications](https://docs.godotengine.org/en/stable/tutorials/best_practices/godot_notifications.html).
1300+
/// * [Notifications tutorial](https://docs.godotengine.org/en/stable/tutorials/best_practices/godot_notifications.html).
11921301
fn on_notification(&mut self, what: #notification_enum_name) {
11931302
unimplemented!()
11941303
}
@@ -1229,7 +1338,7 @@ fn make_virtual_method(class_method: &ClassMethod, ctx: &mut Context) -> TokenSt
12291338

12301339
fn make_all_virtual_methods(
12311340
class: &Class,
1232-
all_bases: &[TyName],
1341+
all_base_names: &[TyName],
12331342
ctx: &mut Context,
12341343
) -> Vec<TokenStream> {
12351344
let mut all_virtuals = vec![];
@@ -1245,7 +1354,7 @@ fn make_all_virtual_methods(
12451354
// Get virtuals defined on the current class.
12461355
extend_virtuals(class);
12471356
// Add virtuals from superclasses.
1248-
for base in all_bases {
1357+
for base in all_base_names {
12491358
let superclass = ctx.get_engine_class(base);
12501359
extend_virtuals(superclass);
12511360
}

godot-codegen/src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,23 +226,24 @@ impl ToTokens for ModName {
226226
}
227227

228228
struct GeneratedClass {
229-
tokens: TokenStream,
229+
code: TokenStream,
230230
notification_enum_name: Ident,
231231
has_own_notification_enum: bool,
232232
inherits_macro_ident: Ident,
233-
has_pub_module: bool,
233+
/// Sidecars are the associated modules with related enum/flag types, such as `node_3d` for `Node3D` class.
234+
has_sidecar_module: bool,
234235
}
235236

236237
struct GeneratedBuiltin {
237-
tokens: TokenStream,
238+
code: TokenStream,
238239
}
239240

240241
struct GeneratedClassModule {
241242
class_name: TyName,
242243
module_name: ModName,
243244
own_notification_enum_name: Option<Ident>,
244245
inherits_macro_ident: Ident,
245-
is_pub: bool,
246+
is_pub_sidecar: bool,
246247
}
247248

248249
struct GeneratedBuiltinModule {

0 commit comments

Comments
 (0)