Skip to content

Commit feaadc1

Browse files
committed
move doc concatenation to runtime
1 parent 44a33f5 commit feaadc1

File tree

8 files changed

+113
-34
lines changed

8 files changed

+113
-34
lines changed

godot-core/src/init/mod.rs

+67-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8+
use std::collections::{hash_map::Entry, HashMap};
89
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
910

1011
use godot_ffi as sys;
@@ -13,6 +14,7 @@ use sys::GodotFfi;
1314

1415
use crate::builtin::{GString, StringName};
1516
use crate::out;
17+
use crate::registry::plugin::PluginItem;
1618

1719
pub use sys::GdextBuild;
1820

@@ -119,6 +121,69 @@ unsafe extern "C" fn ffi_deinitialize_layer<E: ExtensionLibrary>(
119121
});
120122
}
121123

124+
unsafe fn register() {
125+
#[derive(Default)]
126+
struct DocPieces {
127+
/// base, description
128+
definition: [&'static str; 2],
129+
/// methods, signals, constants
130+
inherent: [&'static str; 3],
131+
imethods: &'static str,
132+
}
133+
134+
let mut map = HashMap::<&'static str, DocPieces>::new();
135+
crate::private::iterate_plugins(|x| match x.item {
136+
PluginItem::InherentImpl { docs, .. } => match map.entry(x.class_name.as_str()) {
137+
Entry::Occupied(x) => x.into_mut().inherent = docs,
138+
Entry::Vacant(x) => drop(x.insert(DocPieces {
139+
inherent: docs,
140+
..Default::default()
141+
})),
142+
},
143+
PluginItem::ITraitImpl { docs, .. } => match map.entry(x.class_name.as_str()) {
144+
Entry::Occupied(x) => x.into_mut().imethods = docs,
145+
Entry::Vacant(x) => drop(x.insert(DocPieces {
146+
imethods: docs,
147+
..Default::default()
148+
})),
149+
},
150+
PluginItem::Struct { docs, .. } => match map.entry(x.class_name.as_str()) {
151+
Entry::Occupied(x) => x.into_mut().definition = docs,
152+
Entry::Vacant(x) => drop(x.insert(DocPieces {
153+
definition: docs,
154+
..Default::default()
155+
})),
156+
},
157+
});
158+
for (
159+
class,
160+
DocPieces {
161+
definition: [base, desc],
162+
inherent: [methods, signals, constants],
163+
imethods,
164+
},
165+
) in map
166+
{
167+
let brief = desc.lines().next().unwrap_or_default();
168+
let xml = format!(
169+
r#"
170+
<?xml version="1.0" encoding="UTF-8"?>
171+
<class name="{class}" inherits="{base}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
172+
<brief_description>{brief}</brief_description>
173+
<description>{desc}</description>
174+
<methods>{methods}{imethods}</methods>
175+
<constants>{constants}</constants>
176+
<signals>{signals}</signals>
177+
</class>"#
178+
);
179+
// SAFETY: the godot binding is initialized
180+
crate::sys::interface_fn!(editor_help_load_xml_from_utf8_chars_and_len)(
181+
xml.as_ptr().cast(),
182+
xml.len() as _,
183+
);
184+
}
185+
}
186+
122187
/// Tasks needed to be done by gdext internally upon loading an initialization level. Called before user code.
123188
fn gdext_on_level_init(level: InitLevel) {
124189
// SAFETY: we are in the main thread, during initialization, no other logic is happening.
@@ -136,17 +201,8 @@ fn gdext_on_level_init(level: InitLevel) {
136201
ensure_godot_features_compatible();
137202
}
138203
InitLevel::Editor => {
139-
crate::private::iterate_plugins(|x| match x.item {
140-
crate::registry::plugin::PluginItem::InherentImpl {
141-
register_documentation,
142-
..
143-
} => register_documentation(),
144-
// crate::registry::plugin::PluginItem::ITraitImpl {
145-
// register_documentation,
146-
// ..
147-
// } => register_documentation(),
148-
_ => (),
149-
});
204+
#[cfg(since_api = "4.3")]
205+
register();
150206
sys::load_class_method_table(sys::ClassApiLevel::Editor);
151207
}
152208
}

godot-core/src/registry/class.rs

+1
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
232232
is_editor_plugin,
233233
is_hidden,
234234
is_instantiable,
235+
..
235236
} => {
236237
c.parent_class_name = Some(base_class_name);
237238
c.default_virtual_fn = default_get_virtual_fn;

godot-core/src/registry/plugin.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ pub enum PluginItem {
9696

9797
/// Whether the class has a default constructor.
9898
is_instantiable: bool,
99+
/// Base, Description.
100+
docs: [&'static str; 2],
99101
},
100102

101103
/// Collected from `#[godot_api] impl MyClass`.
@@ -104,14 +106,14 @@ pub enum PluginItem {
104106
///
105107
/// Always present since that's the entire point of this `impl` block.
106108
register_methods_constants_fn: ErasedRegisterFn,
107-
/// Registers the documentation for this class.
108-
register_documentation: unsafe fn(),
109+
/// Method, Signal, Constant documentation.
110+
docs: [&'static str; 3],
109111
},
110112

111113
/// Collected from `#[godot_api] impl I... for MyClass`.
112114
ITraitImpl {
113-
/// Registers the documentation for this class's virtual (`_ready`, …) methods.
114-
register_documentation: unsafe fn(),
115+
/// Virtual method documentation.
116+
docs: &'static str,
115117
/// Callback to user-defined `register_class` function.
116118
user_register_fn: Option<ErasedRegisterFn>,
117119

godot-macros/src/class/data_models/inherent_impl.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub fn transform_inherent_impl(mut impl_block: venial::Impl) -> ParseResult<Toke
6262
let constant_registration =
6363
make_constant_registration(consts.clone(), &class_name, &class_name_obj)?;
6464

65-
let docs = crate::docs::class(funcs, None, consts, signals);
65+
let docs = crate::docs::inherent_impl(funcs, consts, signals);
6666
let result = quote! {
6767
#impl_block
6868

@@ -80,7 +80,7 @@ pub fn transform_inherent_impl(mut impl_block: venial::Impl) -> ParseResult<Toke
8080
::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
8181
class_name: #class_name_obj,
8282
item: #prv::PluginItem::InherentImpl {
83-
register_documentation: { #docs docs::<#class_name> },
83+
docs: #docs,
8484
register_methods_constants_fn: #prv::ErasedRegisterFn {
8585
raw: #prv::callbacks::register_user_methods_constants::<#class_name>,
8686
},

godot-macros/src/class/data_models/interface_trait_impl.rs

+5-10
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,10 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
4242
let mut virtual_method_names = vec![];
4343

4444
let prv = quote! { ::godot::private };
45-
let docs = crate::docs::class(
46-
None,
47-
original_impl.body_items.iter().filter_map(|x| match x {
48-
venial::ImplMember::AssocFunction(f) => Some(f.clone()),
49-
_ => None,
50-
}),
51-
None,
52-
None,
53-
);
45+
let docs = crate::docs::virtual_impl(original_impl.body_items.iter().filter_map(|x| match x {
46+
venial::ImplMember::AssocFunction(f) => Some(f.clone()),
47+
_ => None,
48+
}));
5449
for item in original_impl.body_items.iter() {
5550
let method = if let venial::ImplMember::AssocFunction(f) = item {
5651
f
@@ -396,7 +391,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
396391
::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
397392
class_name: #class_name_obj,
398393
item: #prv::PluginItem::ITraitImpl {
399-
register_documentation: { #docs docs::<#class_name> },
394+
docs: #docs,
400395
user_register_fn: #register_fn,
401396
user_create_fn: #create_fn,
402397
user_recreate_fn: #recreate_fn,

godot-macros/src/class/derive_godot_class.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,14 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
104104
};
105105

106106
let is_tool = struct_cfg.is_tool;
107-
108-
let docs = crate::docs::docs(class.attributes.clone());
107+
let docs = crate::docs::define(base_class.to_string(), class.attributes.clone());
109108
Ok(quote! {
110109
impl ::godot::obj::GodotClass for #class_name {
111110
type Base = #base_class;
112111

113112
fn class_name() -> ::godot::meta::ClassName {
114113
::godot::meta::ClassName::from_ascii_cstr(#class_name_cstr)
115114
}
116-
117-
const __DOCS: &'static str = #docs;
118-
const __BASE: ::core::option::Option<&'static str> = Some(stringify!(#base_ty));
119115
}
120116

121117
unsafe impl ::godot::obj::Bounds for #class_name {
@@ -133,6 +129,7 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
133129
::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
134130
class_name: #class_name_obj,
135131
item: #prv::PluginItem::Struct {
132+
docs: #docs,
136133
base_class_name: #base_class_name_obj,
137134
generated_create_fn: #create_fn,
138135
generated_recreate_fn: #recreate_fn,

godot-macros/src/docs.rs

+29-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ use markdown::mdast::Node;
33
use markdown::{to_mdast, ParseOptions};
44
use proc_macro2::TokenStream;
55
use quote::ToTokens;
6+
use std::cell::OnceCell;
7+
use std::collections::hash_map::Entry;
68
use std::collections::HashMap;
9+
use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError};
10+
use std::time::Duration;
711
use venial::*;
812

913
/// Converts [markdown](https://en.wikipedia.org/wiki/Markdown) to [BBCode](https://en.wikipedia.org/wiki/BBCode).
@@ -89,6 +93,30 @@ pub fn constant(cnst: Constant) -> String {
8993
format!(r#"<constant name="{name}" value="{value}">{docs}</constant>"#)
9094
}
9195

96+
pub fn define(base: String, description: Vec<Attribute>) -> TokenStream {
97+
let desc = docs(description);
98+
quote::quote![[#base, #desc]]
99+
}
100+
101+
pub fn inherent_impl(
102+
functions: impl IntoIterator<Item = FuncDefinition>,
103+
constants: impl IntoIterator<Item = ConstDefinition>,
104+
signals: impl IntoIterator<Item = SignalDefinition>,
105+
) -> TokenStream {
106+
let methods = functions.into_iter().map(method).collect::<String>();
107+
let signals = signals.into_iter().map(signal).collect::<String>();
108+
let constants = constants
109+
.into_iter()
110+
.map(|ConstDefinition { raw_constant: x }| x)
111+
.map(constant)
112+
.collect::<String>();
113+
quote::quote![[#methods, #signals, #constants]]
114+
}
115+
116+
pub fn virtual_impl(imethods: impl IntoIterator<Item = Function>) -> String {
117+
imethods.into_iter().map(imethod).collect::<String>()
118+
}
119+
92120
/// This will register the documentation.
93121
pub fn class(
94122
functions: impl IntoIterator<Item = FuncDefinition>,
@@ -112,7 +140,7 @@ pub fn class(
112140

113141
let init = format!(
114142
r#"
115-
<?xml version="1.0" encoding="UTF-8" ?>
143+
<?xml version="1.0" encoding="UTF-8"?>
116144
<class name="CLASS⯂" inherits="BASE⯂"
117145
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
118146
<brief_description>BRIEF⯂</brief_description>

itest/rust/src/register_tests/constant_test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ godot::sys::plugin_add!(
171171
::godot::private::ClassPlugin {
172172
class_name: HasOtherConstants::class_name(),
173173
item: ::godot::private::PluginItem::InherentImpl {
174-
register_documentation: { unsafe fn x() {} x },
174+
docs: ["";3],
175175
register_methods_constants_fn: ::godot::private::ErasedRegisterFn {
176176
raw: ::godot::private::callbacks::register_user_methods_constants::<HasOtherConstants>,
177177
},

0 commit comments

Comments
 (0)