Skip to content

Commit e1b84ba

Browse files
committed
Doc bugfixes and improvements
- Bugfix: Handle docs in `#[godot_api(secondary)]` - Code tweaks: Separate docs modules to not pollute rest of the code with `#[cfg(all(feature = "register-docs", since_api = "4.3"))]`. Simplify registration logic. - Create separate PluginRegistry for Docs and Docs only.
1 parent fe94254 commit e1b84ba

File tree

13 files changed

+284
-162
lines changed

13 files changed

+284
-162
lines changed

godot-core/src/docs.rs

Lines changed: 97 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,41 @@
88
use std::collections::HashMap;
99

1010
use crate::meta::ClassId;
11-
use crate::registry::plugin::{ITraitImpl, InherentImpl, PluginItem, Struct};
11+
use crate::obj::GodotClass;
12+
13+
/// Piece of information that is gathered by the self-registration ("plugin") system.
14+
///
15+
/// You should not manually construct this struct, but rather use [`DocsPlugin::new()`].
16+
#[derive(Debug)]
17+
pub struct DocsPlugin {
18+
/// The name of the class to register docs for.
19+
class_name: ClassId,
20+
21+
/// The actual item being registered.
22+
item: DocsItem,
23+
}
24+
25+
impl DocsPlugin {
26+
/// Creates a new `DocsPlugin`, automatically setting the `class_name` to the values defined in [`GodotClass`].
27+
pub fn new<T: GodotClass>(item: DocsItem) -> Self {
28+
Self {
29+
class_name: T::class_id(),
30+
item,
31+
}
32+
}
33+
}
34+
35+
type TraitImplDocs = &'static str;
36+
37+
#[derive(Debug)]
38+
pub enum DocsItem {
39+
/// Docs for `#[derive(GodotClass)] struct MyClass`.
40+
Struct(StructDocs),
41+
/// Docs for `#[godot_api] impl MyClass`.
42+
InherentImpl(InherentImplDocs),
43+
/// Docs for `#[godot_api] impl ITrait for MyClass`.
44+
ITraitImpl(TraitImplDocs),
45+
}
1246

1347
/// Created for documentation on
1448
/// ```ignore
@@ -29,7 +63,7 @@ pub struct StructDocs {
2963
pub members: &'static str,
3064
}
3165

32-
/// Keeps documentation for inherent `impl` blocks, such as:
66+
/// Keeps documentation for inherent `impl` blocks (primary and secondary), such as:
3367
/// ```ignore
3468
/// #[godot_api]
3569
/// impl Struct {
@@ -46,18 +80,19 @@ pub struct StructDocs {
4680
/// }
4781
/// ```
4882
/// All fields are XML parts, escaped where necessary.
49-
#[derive(Default, Copy, Clone, Debug)]
83+
#[derive(Default, Clone, Debug)]
5084
pub struct InherentImplDocs {
51-
pub methods: Vec<&'static str>,
52-
pub signals: Vec<&'static str>,
53-
pub constants: Vec<&'static str>,
85+
pub methods: &'static str,
86+
pub signals: &'static str,
87+
pub constants: &'static str,
5488
}
5589

5690
#[derive(Default)]
5791
struct DocPieces {
5892
definition: StructDocs,
59-
inherent: InherentImplDocs,
60-
virtual_methods: Vec<&'static str>,
93+
methods: Vec<&'static str>,
94+
signals: Vec<&'static str>,
95+
constants: Vec<&'static str>,
6196
}
6297

6398
/// This function scours the registered plugins to find their documentation pieces,
@@ -76,68 +111,66 @@ struct DocPieces {
76111
#[doc(hidden)]
77112
pub fn gather_xml_docs() -> impl Iterator<Item = String> {
78113
let mut map = HashMap::<ClassId, DocPieces>::new();
79-
crate::private::iterate_plugins(|x| {
114+
crate::private::iterate_docs_plugins(|x| {
80115
let class_name = x.class_name;
81-
82116
match &x.item {
83-
PluginItem::InherentImpl(InherentImpl { docs, .. }) => {
84-
map.entry(class_name).or_default().inherent = docs.clone();
117+
DocsItem::Struct(s) => {
118+
map.entry(class_name).or_default().definition = *s;
85119
}
86-
PluginItem::ITraitImpl(ITraitImpl {
87-
virtual_method_docs,
88-
..
89-
}) => map
90-
.entry(class_name)
91-
.or_default()
92-
.virtual_methods
93-
.push(virtual_method_docs),
94-
95-
PluginItem::Struct(Struct { docs, .. }) => {
96-
map.entry(class_name).or_default().definition = *docs;
120+
DocsItem::InherentImpl(trait_docs) => {
121+
let InherentImplDocs {
122+
methods,
123+
constants,
124+
signals,
125+
} = trait_docs;
126+
map.entry(class_name).or_default().methods.push(methods);
127+
map.entry(class_name)
128+
.and_modify(|pieces| pieces.constants.push(constants));
129+
map.entry(class_name)
130+
.and_modify(|pieces| pieces.signals.push(signals));
131+
}
132+
DocsItem::ITraitImpl(methods) => {
133+
map.entry(class_name).or_default().methods.push(methods);
97134
}
98-
99-
_ => (),
100135
}
101136
});
102137

103138
map.into_iter().map(|(class, pieces)| {
104-
let StructDocs {
105-
base,
106-
description,
107-
experimental,
108-
deprecated,
109-
members,
110-
} = pieces.definition;
111-
112-
let virtual_methods = pieces.virtual_methods;
113-
114-
let mut method_docs = String::from_iter(pieces.inherent.methods);
115-
let signal_docs = String::from_iter(pieces.inherent.signals);
116-
let constant_docs = String::from_iter(pieces.inherent.constants);
117-
118-
method_docs.extend(virtual_methods);
119-
let methods_block = if method_docs.is_empty() {
120-
String::new()
121-
} else {
122-
format!("<methods>{method_docs}</methods>")
123-
};
124-
let signals_block = if signal_docs.is_empty() {
125-
String::new()
126-
} else {
127-
format!("<signals>{signal_docs}</signals>")
128-
};
129-
let constants_block = if constant_docs.is_empty() {
130-
String::new()
131-
} else {
132-
format!("<constants>{constant_docs}</constants>")
133-
};
134-
let (brief, description) = match description
135-
.split_once("[br]") {
136-
Some((brief, description)) => (brief, description.trim_start_matches("[br]")),
137-
None => (description, ""),
138-
};
139-
140-
format!(r#"<?xml version="1.0" encoding="UTF-8"?>
139+
let StructDocs {
140+
base,
141+
description,
142+
experimental,
143+
deprecated,
144+
members,
145+
} = pieces.definition;
146+
147+
148+
let method_docs = String::from_iter(pieces.methods);
149+
let signal_docs = String::from_iter(pieces.signals);
150+
let constant_docs = String::from_iter(pieces.constants);
151+
152+
let methods_block = if method_docs.is_empty() {
153+
String::new()
154+
} else {
155+
format!("<methods>{method_docs}</methods>")
156+
};
157+
let signals_block = if signal_docs.is_empty() {
158+
String::new()
159+
} else {
160+
format!("<signals>{signal_docs}</signals>")
161+
};
162+
let constants_block = if constant_docs.is_empty() {
163+
String::new()
164+
} else {
165+
format!("<constants>{constant_docs}</constants>")
166+
};
167+
let (brief, description) = match description
168+
.split_once("[br]") {
169+
Some((brief, description)) => (brief, description.trim_start_matches("[br]")),
170+
None => (description, ""),
171+
};
172+
173+
format!(r#"<?xml version="1.0" encoding="UTF-8"?>
141174
<class name="{class}" inherits="{base}"{deprecated}{experimental} xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
142175
<brief_description>{brief}</brief_description>
143176
<description>{description}</description>
@@ -146,8 +179,8 @@ pub fn gather_xml_docs() -> impl Iterator<Item = String> {
146179
{signals_block}
147180
<members>{members}</members>
148181
</class>"#)
149-
},
150-
)
182+
},
183+
)
151184
}
152185

153186
/// # Safety

godot-core/src/private.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ use crate::{classes, sys};
2222
// Public re-exports
2323

2424
mod reexport_pub {
25+
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
26+
pub use crate::docs::{DocsItem, DocsPlugin, InherentImplDocs, StructDocs};
2527
pub use crate::gen::classes::class_macros;
2628
#[cfg(feature = "trace")]
2729
pub use crate::meta::trace;
@@ -52,6 +54,8 @@ static CALL_ERRORS: Global<CallErrors> = Global::default();
5254
static ERROR_PRINT_LEVEL: atomic::AtomicU8 = atomic::AtomicU8::new(2);
5355

5456
sys::plugin_registry!(pub __GODOT_PLUGIN_REGISTRY: ClassPlugin);
57+
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
58+
sys::plugin_registry!(pub __GODOT_DOCS_REGISTRY: DocsPlugin);
5559

5660
// ----------------------------------------------------------------------------------------------------------------------------------------------
5761
// Call error handling
@@ -142,26 +146,15 @@ pub fn next_class_id() -> u16 {
142146
NEXT_CLASS_ID.fetch_add(1, atomic::Ordering::Relaxed)
143147
}
144148

145-
// Don't touch unless you know what you're doing.
146-
#[doc(hidden)]
147-
pub fn edit_inherent_impl(class_name: crate::meta::ClassName, f: impl FnOnce(&mut InherentImpl)) {
148-
let mut plugins = __GODOT_PLUGIN_REGISTRY.lock().unwrap();
149-
150-
for elem in plugins.iter_mut().filter(|p| p.class_name == class_name) {
151-
match &mut elem.item {
152-
PluginItem::InherentImpl(inherent_impl) => {
153-
f(inherent_impl);
154-
return;
155-
}
156-
PluginItem::Struct(_) | PluginItem::ITraitImpl(_) | PluginItem::DynTraitImpl(_) => {}
157-
}
158-
}
159-
}
160-
161149
pub(crate) fn iterate_plugins(mut visitor: impl FnMut(&ClassPlugin)) {
162150
sys::plugin_foreach!(__GODOT_PLUGIN_REGISTRY; visitor);
163151
}
164152

153+
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
154+
pub(crate) fn iterate_docs_plugins(mut visitor: impl FnMut(&DocsPlugin)) {
155+
sys::plugin_foreach!(__GODOT_DOCS_REGISTRY; visitor);
156+
}
157+
165158
#[cfg(feature = "codegen-full")] // Remove if used in other scenarios.
166159
pub(crate) fn find_inherent_impl(class_name: crate::meta::ClassId) -> Option<InherentImpl> {
167160
// We do this manually instead of using `iterate_plugins()` because we want to break as soon as we find a match.

godot-core/src/registry/class.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,7 @@ impl ClassRegistrationInfo {
115115
// Note: when changing this match, make sure the array has sufficient size.
116116
let index = match item {
117117
PluginItem::Struct { .. } => 0,
118-
PluginItem::InherentImpl(_) => {
119-
// Inherent impls don't need to be unique.
120-
return;
121-
}
118+
PluginItem::InherentImpl(_) => 1,
122119
PluginItem::ITraitImpl { .. } => 2,
123120

124121
// Multiple dyn traits can be registered, thus don't validate for uniqueness.
@@ -427,8 +424,6 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
427424
is_editor_plugin,
428425
is_internal,
429426
is_instantiable,
430-
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
431-
docs: _,
432427
reference_fn,
433428
unreference_fn,
434429
}) => {
@@ -476,8 +471,6 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
476471
PluginItem::InherentImpl(InherentImpl {
477472
register_methods_constants_fn,
478473
register_rpcs_fn: _,
479-
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
480-
docs: _,
481474
}) => {
482475
c.register_methods_constants_fn = Some(register_methods_constants_fn);
483476
}
@@ -495,8 +488,6 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
495488
user_free_property_list_fn,
496489
user_property_can_revert_fn,
497490
user_property_get_revert_fn,
498-
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
499-
virtual_method_docs: _,
500491
validate_property_fn,
501492
}) => {
502493
c.user_register_fn = user_register_fn;

godot-core/src/registry/plugin.rs

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
use std::any::Any;
99
use std::{any, fmt};
1010

11-
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
12-
use crate::docs::*;
1311
use crate::init::InitLevel;
1412
use crate::meta::ClassId;
1513
use crate::obj::{bounds, cap, Bounds, DynGd, Gd, GodotClass, Inherits, UserClass};
@@ -39,10 +37,10 @@ pub struct ClassPlugin {
3937
/// Incorrectly setting this value should not cause any UB but will likely cause errors during registration time.
4038
// Init-level is per ClassPlugin and not per PluginItem, because all components of all classes are mixed together in one
4139
// huge linker list. There is no per-class aggregation going on, so this allows to easily filter relevant classes.
42-
pub init_level: InitLevel,
40+
pub(crate) init_level: InitLevel,
4341

4442
/// The actual item being registered.
45-
pub item: PluginItem,
43+
pub(crate) item: PluginItem,
4644
}
4745

4846
impl ClassPlugin {
@@ -197,16 +195,10 @@ pub struct Struct {
197195

198196
/// Whether the class has a default constructor.
199197
pub(crate) is_instantiable: bool,
200-
201-
/// Documentation extracted from the struct's RustDoc.
202-
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
203-
pub(crate) docs: StructDocs,
204198
}
205199

206200
impl Struct {
207-
pub fn new<T: GodotClass + cap::ImplementsGodotExports>(
208-
#[cfg(all(since_api = "4.3", feature = "register-docs"))] docs: StructDocs,
209-
) -> Self {
201+
pub fn new<T: GodotClass + cap::ImplementsGodotExports>() -> Self {
210202
let refcounted = <T::Memory as bounds::Memory>::IS_REF_COUNTED;
211203

212204
Self {
@@ -222,8 +214,6 @@ impl Struct {
222214
is_editor_plugin: false,
223215
is_internal: false,
224216
is_instantiable: false,
225-
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
226-
docs,
227217
// While Godot doesn't do anything with these callbacks for non-RefCounted classes, we can avoid instantiating them in Rust.
228218
reference_fn: refcounted.then_some(callbacks::reference::<T>),
229219
unreference_fn: refcounted.then_some(callbacks::unreference::<T>),
@@ -294,9 +284,6 @@ pub struct InherentImpl {
294284
// This field is only used during codegen-full.
295285
#[cfg_attr(not(feature = "codegen-full"), expect(dead_code))]
296286
pub(crate) register_rpcs_fn: Option<ErasedRegisterRpcsFn>,
297-
298-
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
299-
pub docs: InherentImplDocs,
300287
}
301288

302289
impl InherentImpl {
@@ -308,18 +295,12 @@ impl InherentImpl {
308295
register_rpcs_fn: Some(ErasedRegisterRpcsFn {
309296
raw: callbacks::register_user_rpcs::<T>,
310297
}),
311-
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
312-
docs: Default::default(),
313298
}
314299
}
315300
}
316301

317302
#[derive(Default, Clone, Debug)]
318303
pub struct ITraitImpl {
319-
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
320-
/// Virtual method documentation.
321-
pub(crate) virtual_method_docs: &'static str,
322-
323304
/// Callback to user-defined `register_class` function.
324305
pub(crate) user_register_fn: Option<ErasedRegisterFn>,
325306

@@ -434,12 +415,8 @@ pub struct ITraitImpl {
434415
}
435416

436417
impl ITraitImpl {
437-
pub fn new<T: GodotClass + cap::ImplementsGodotVirtual>(
438-
#[cfg(all(since_api = "4.3", feature = "register-docs"))] virtual_method_docs: &'static str,
439-
) -> Self {
418+
pub fn new<T: GodotClass + cap::ImplementsGodotVirtual>() -> Self {
440419
Self {
441-
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
442-
virtual_method_docs,
443420
get_virtual_fn: Some(callbacks::get_virtual::<T>),
444421
..Default::default()
445422
}

0 commit comments

Comments
 (0)