diff --git a/godot-core/src/docs.rs b/godot-core/src/docs.rs index beaba7219..57ca6d850 100644 --- a/godot-core/src/docs.rs +++ b/godot-core/src/docs.rs @@ -8,7 +8,41 @@ use std::collections::HashMap; use crate::meta::ClassId; -use crate::registry::plugin::{ITraitImpl, InherentImpl, PluginItem, Struct}; +use crate::obj::GodotClass; + +/// Piece of information that is gathered by the self-registration ("plugin") system. +/// +/// You should not manually construct this struct, but rather use [`DocsPlugin::new()`]. +#[derive(Debug)] +pub struct DocsPlugin { + /// The name of the class to register docs for. + class_name: ClassId, + + /// The actual item being registered. + item: DocsItem, +} + +impl DocsPlugin { + /// Creates a new `DocsPlugin`, automatically setting the `class_name` to the values defined in [`GodotClass`]. + pub fn new(item: DocsItem) -> Self { + Self { + class_name: T::class_id(), + item, + } + } +} + +type TraitImplDocs = &'static str; + +#[derive(Debug)] +pub enum DocsItem { + /// Docs for `#[derive(GodotClass)] struct MyClass`. + Struct(StructDocs), + /// Docs for `#[godot_api] impl MyClass`. + InherentImpl(InherentImplDocs), + /// Docs for `#[godot_api] impl ITrait for MyClass`. + ITraitImpl(TraitImplDocs), +} /// Created for documentation on /// ```ignore @@ -29,7 +63,7 @@ pub struct StructDocs { pub members: &'static str, } -/// Keeps documentation for inherent `impl` blocks, such as: +/// Keeps documentation for inherent `impl` blocks (primary and secondary), such as: /// ```ignore /// #[godot_api] /// impl Struct { @@ -46,18 +80,19 @@ pub struct StructDocs { /// } /// ``` /// All fields are XML parts, escaped where necessary. -#[derive(Default, Copy, Clone, Debug)] +#[derive(Default, Clone, Debug)] pub struct InherentImplDocs { pub methods: &'static str, - pub signals_block: &'static str, - pub constants_block: &'static str, + pub signals: &'static str, + pub constants: &'static str, } #[derive(Default)] struct DocPieces { definition: StructDocs, - inherent: InherentImplDocs, - virtual_methods: &'static str, + methods: Vec<&'static str>, + signals: Vec<&'static str>, + constants: Vec<&'static str>, } /// This function scours the registered plugins to find their documentation pieces, @@ -76,24 +111,27 @@ struct DocPieces { #[doc(hidden)] pub fn gather_xml_docs() -> impl Iterator { let mut map = HashMap::::new(); - crate::private::iterate_plugins(|x| { + crate::private::iterate_docs_plugins(|x| { let class_name = x.class_name; - - match x.item { - PluginItem::InherentImpl(InherentImpl { docs, .. }) => { - map.entry(class_name).or_default().inherent = docs + match &x.item { + DocsItem::Struct(s) => { + map.entry(class_name).or_default().definition = *s; } - - PluginItem::ITraitImpl(ITraitImpl { - virtual_method_docs, - .. - }) => map.entry(class_name).or_default().virtual_methods = virtual_method_docs, - - PluginItem::Struct(Struct { docs, .. }) => { - map.entry(class_name).or_default().definition = docs + DocsItem::InherentImpl(trait_docs) => { + let InherentImplDocs { + methods, + constants, + signals, + } = trait_docs; + map.entry(class_name).or_default().methods.push(methods); + map.entry(class_name) + .and_modify(|pieces| pieces.constants.push(constants)); + map.entry(class_name) + .and_modify(|pieces| pieces.signals.push(signals)); + } + DocsItem::ITraitImpl(methods) => { + map.entry(class_name).or_default().methods.push(methods); } - - _ => (), } }); @@ -106,22 +144,31 @@ pub fn gather_xml_docs() -> impl Iterator { members, } = pieces.definition; - let InherentImplDocs { - methods, - signals_block, - constants_block, - } = pieces.inherent; - - let virtual_methods = pieces.virtual_methods; - let methods_block = (virtual_methods.is_empty() && methods.is_empty()) - .then(String::new) - .unwrap_or_else(|| format!("{methods}{virtual_methods}")); + let method_docs = String::from_iter(pieces.methods); + let signal_docs = String::from_iter(pieces.signals); + let constant_docs = String::from_iter(pieces.constants); + + let methods_block = if method_docs.is_empty() { + String::new() + } else { + format!("{method_docs}") + }; + let signals_block = if signal_docs.is_empty() { + String::new() + } else { + format!("{signal_docs}") + }; + let constants_block = if constant_docs.is_empty() { + String::new() + } else { + format!("{constant_docs}") + }; let (brief, description) = match description .split_once("[br]") { - Some((brief, description)) => (brief, description.trim_start_matches("[br]")), - None => (description, ""), - }; + Some((brief, description)) => (brief, description.trim_start_matches("[br]")), + None => (description, ""), + }; format!(r#" diff --git a/godot-core/src/private.rs b/godot-core/src/private.rs index 74aaed0ef..9f1ac4009 100644 --- a/godot-core/src/private.rs +++ b/godot-core/src/private.rs @@ -22,6 +22,8 @@ use crate::{classes, sys}; // Public re-exports mod reexport_pub { + #[cfg(all(since_api = "4.3", feature = "register-docs"))] + pub use crate::docs::{DocsItem, DocsPlugin, InherentImplDocs, StructDocs}; pub use crate::gen::classes::class_macros; #[cfg(feature = "trace")] pub use crate::meta::trace; @@ -52,6 +54,8 @@ static CALL_ERRORS: Global = Global::default(); static ERROR_PRINT_LEVEL: atomic::AtomicU8 = atomic::AtomicU8::new(2); sys::plugin_registry!(pub __GODOT_PLUGIN_REGISTRY: ClassPlugin); +#[cfg(all(since_api = "4.3", feature = "register-docs"))] +sys::plugin_registry!(pub __GODOT_DOCS_REGISTRY: DocsPlugin); // ---------------------------------------------------------------------------------------------------------------------------------------------- // Call error handling @@ -146,6 +150,11 @@ pub(crate) fn iterate_plugins(mut visitor: impl FnMut(&ClassPlugin)) { sys::plugin_foreach!(__GODOT_PLUGIN_REGISTRY; visitor); } +#[cfg(all(since_api = "4.3", feature = "register-docs"))] +pub(crate) fn iterate_docs_plugins(mut visitor: impl FnMut(&DocsPlugin)) { + sys::plugin_foreach!(__GODOT_DOCS_REGISTRY; visitor); +} + #[cfg(feature = "codegen-full")] // Remove if used in other scenarios. pub(crate) fn find_inherent_impl(class_name: crate::meta::ClassId) -> Option { // We do this manually instead of using `iterate_plugins()` because we want to break as soon as we find a match. diff --git a/godot-core/src/registry/class.rs b/godot-core/src/registry/class.rs index 4a13622d5..1715313ec 100644 --- a/godot-core/src/registry/class.rs +++ b/godot-core/src/registry/class.rs @@ -424,8 +424,6 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { is_editor_plugin, is_internal, is_instantiable, - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - docs: _, reference_fn, unreference_fn, }) => { @@ -473,8 +471,6 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { PluginItem::InherentImpl(InherentImpl { register_methods_constants_fn, register_rpcs_fn: _, - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - docs: _, }) => { c.register_methods_constants_fn = Some(register_methods_constants_fn); } @@ -492,8 +488,6 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { user_free_property_list_fn, user_property_can_revert_fn, user_property_get_revert_fn, - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - virtual_method_docs: _, validate_property_fn, }) => { c.user_register_fn = user_register_fn; diff --git a/godot-core/src/registry/plugin.rs b/godot-core/src/registry/plugin.rs index 7d2ad995f..f03da5fae 100644 --- a/godot-core/src/registry/plugin.rs +++ b/godot-core/src/registry/plugin.rs @@ -8,8 +8,6 @@ use std::any::Any; use std::{any, fmt}; -#[cfg(all(since_api = "4.3", feature = "register-docs"))] -use crate::docs::*; use crate::init::InitLevel; use crate::meta::ClassId; use crate::obj::{bounds, cap, Bounds, DynGd, Gd, GodotClass, Inherits, UserClass}; @@ -197,16 +195,10 @@ pub struct Struct { /// Whether the class has a default constructor. pub(crate) is_instantiable: bool, - - /// Documentation extracted from the struct's RustDoc. - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - pub(crate) docs: StructDocs, } impl Struct { - pub fn new( - #[cfg(all(since_api = "4.3", feature = "register-docs"))] docs: StructDocs, - ) -> Self { + pub fn new() -> Self { let refcounted = ::IS_REF_COUNTED; Self { @@ -222,8 +214,6 @@ impl Struct { is_editor_plugin: false, is_internal: false, is_instantiable: false, - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - docs, // While Godot doesn't do anything with these callbacks for non-RefCounted classes, we can avoid instantiating them in Rust. reference_fn: refcounted.then_some(callbacks::reference::), unreference_fn: refcounted.then_some(callbacks::unreference::), @@ -294,15 +284,10 @@ pub struct InherentImpl { // This field is only used during codegen-full. #[cfg_attr(not(feature = "codegen-full"), expect(dead_code))] pub(crate) register_rpcs_fn: Option, - - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - pub docs: InherentImplDocs, } impl InherentImpl { - pub fn new( - #[cfg(all(since_api = "4.3", feature = "register-docs"))] docs: InherentImplDocs, - ) -> Self { + pub fn new() -> Self { Self { register_methods_constants_fn: ErasedRegisterFn { raw: callbacks::register_user_methods_constants::, @@ -310,18 +295,12 @@ impl InherentImpl { register_rpcs_fn: Some(ErasedRegisterRpcsFn { raw: callbacks::register_user_rpcs::, }), - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - docs, } } } #[derive(Default, Clone, Debug)] pub struct ITraitImpl { - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - /// Virtual method documentation. - pub(crate) virtual_method_docs: &'static str, - /// Callback to user-defined `register_class` function. pub(crate) user_register_fn: Option, @@ -436,12 +415,8 @@ pub struct ITraitImpl { } impl ITraitImpl { - pub fn new( - #[cfg(all(since_api = "4.3", feature = "register-docs"))] virtual_method_docs: &'static str, - ) -> Self { + pub fn new() -> Self { Self { - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - virtual_method_docs, get_virtual_fn: Some(callbacks::get_virtual::), ..Default::default() } diff --git a/godot-macros/src/class/data_models/inherent_impl.rs b/godot-macros/src/class/data_models/inherent_impl.rs index 63f988ad5..2bcd8ac74 100644 --- a/godot-macros/src/class/data_models/inherent_impl.rs +++ b/godot-macros/src/class/data_models/inherent_impl.rs @@ -91,10 +91,8 @@ pub fn transform_inherent_impl( let (funcs, signals) = process_godot_fns(&class_name, &mut impl_block, meta.secondary)?; let consts = process_godot_constants(&mut impl_block)?; - #[cfg(all(feature = "register-docs", since_api = "4.3"))] - let docs = crate::docs::document_inherent_impl(&funcs, &consts, &signals); - #[cfg(not(all(feature = "register-docs", since_api = "4.3")))] - let docs = quote! {}; + let inherent_impl_docs = + crate::docs::make_trait_docs_registration(&funcs, &consts, &signals, &class_name, &prv); // Container struct holding names of all registered #[func]s. // The struct is declared by #[derive(GodotClass)]. @@ -127,17 +125,20 @@ pub fn transform_inherent_impl( let method_storage_name = format_ident!("__registration_methods_{class_name}"); let constants_storage_name = format_ident!("__registration_constants_{class_name}"); - let fill_storage = quote! { - ::godot::sys::plugin_execute_pre_main!({ - #method_storage_name.lock().unwrap().push(|| { - #( #method_registrations )* - #( #signal_registrations )* - }); + let fill_storage = { + quote! { + ::godot::sys::plugin_execute_pre_main!({ + #method_storage_name.lock().unwrap().push(|| { + #( #method_registrations )* + #( #signal_registrations )* + }); + + #constants_storage_name.lock().unwrap().push(|| { + #constant_registration + }); - #constants_storage_name.lock().unwrap().push(|| { - #constant_registration }); - }); + } }; if !meta.secondary { @@ -175,7 +176,7 @@ pub fn transform_inherent_impl( let class_registration = quote! { ::godot::sys::plugin_add!(#prv::__GODOT_PLUGIN_REGISTRY; #prv::ClassPlugin::new::<#class_name>( - #prv::PluginItem::InherentImpl(#prv::InherentImpl::new::<#class_name>(#docs)) + #prv::PluginItem::InherentImpl(#prv::InherentImpl::new::<#class_name>()) )); }; @@ -189,6 +190,7 @@ pub fn transform_inherent_impl( #( #func_name_constants )* } #signal_symbol_types + #inherent_impl_docs }; Ok(result) @@ -202,6 +204,7 @@ pub fn transform_inherent_impl( impl #funcs_collection { #( #func_name_constants )* } + #inherent_impl_docs }; Ok(result) diff --git a/godot-macros/src/class/data_models/interface_trait_impl.rs b/godot-macros/src/class/data_models/interface_trait_impl.rs index 5d78ef3ca..e373cdce7 100644 --- a/godot-macros/src/class/data_models/interface_trait_impl.rs +++ b/godot-macros/src/class/data_models/interface_trait_impl.rs @@ -20,10 +20,11 @@ pub fn transform_trait_impl(mut original_impl: venial::Impl) -> ParseResult ParseResult(#docs); + let mut item = #prv::ITraitImpl::new::<#class_name>(); #(#modifications)* item } @@ -202,6 +203,8 @@ pub fn transform_trait_impl(mut original_impl: venial::Impl) -> ParseResult( #prv::PluginItem::ITraitImpl(#item_constructor) )); + + #register_docs }; // #decls still holds a mutable borrow to `original_impl`, so we mutate && append it afterwards. diff --git a/godot-macros/src/class/derive_godot_class.rs b/godot-macros/src/class/derive_godot_class.rs index ea6057997..09d5c5f96 100644 --- a/godot-macros/src/class/derive_godot_class.rs +++ b/godot-macros/src/class/derive_godot_class.rs @@ -69,18 +69,21 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult { modifiers.push(quote! { with_internal }) } let base_ty = &struct_cfg.base_ty; - #[cfg(all(feature = "register-docs", since_api = "4.3"))] - let docs = - crate::docs::document_struct(base_ty.to_string(), &class.attributes, &fields.all_fields); - #[cfg(not(all(feature = "register-docs", since_api = "4.3")))] - let docs = quote! {}; + let prv = quote! { ::godot::private }; + + let struct_docs_registration = crate::docs::make_struct_docs_registration( + base_ty.to_string(), + &class.attributes, + &fields.all_fields, + class_name, + &prv, + ); let base_class = quote! { ::godot::classes::#base_ty }; // Use this name because when typing a non-existent class, users will be met with the following error: // could not find `inherit_from_OS__ensure_class_exists` in `class_macros`. let inherits_macro_ident = format_ident!("inherit_from_{}__ensure_class_exists", base_ty); - let prv = quote! { ::godot::private }; let godot_exports_impl = make_property_impl(class_name, &fields); let godot_withbase_impl = if let Some(Field { name, ty, .. }) = &fields.base_field { @@ -196,9 +199,10 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult { #( #deprecations )* #( #errors )* + #struct_docs_registration ::godot::sys::plugin_add!(#prv::__GODOT_PLUGIN_REGISTRY; #prv::ClassPlugin::new::<#class_name>( #prv::PluginItem::Struct( - #prv::Struct::new::<#class_name>(#docs)#(.#modifiers())* + #prv::Struct::new::<#class_name>()#(.#modifiers())* ) )); diff --git a/godot-macros/src/docs.rs b/godot-macros/src/docs/extract_docs.rs similarity index 95% rename from godot-macros/src/docs.rs rename to godot-macros/src/docs/extract_docs.rs index de57c209b..44ed4fd62 100644 --- a/godot-macros/src/docs.rs +++ b/godot-macros/src/docs/extract_docs.rs @@ -4,13 +4,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -mod markdown_converter; - use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; use crate::class::{ConstDefinition, Field, FuncDefinition, SignalDefinition}; +use crate::docs::markdown_converter; #[derive(Default)] struct XmlParagraphs { @@ -24,6 +22,12 @@ struct XmlParagraphs { deprecated_attr: String, } +pub struct InherentImplXmlDocs { + pub method_xml_elems: String, + pub constant_xml_elems: String, + pub signal_xml_elems: String, +} + /// Returns code containing the doc information of a `#[derive(GodotClass)] struct MyClass` declaration iff class or any of its members is documented. pub fn document_struct( base: String, @@ -59,39 +63,27 @@ pub fn document_inherent_impl( functions: &[FuncDefinition], constants: &[ConstDefinition], signals: &[SignalDefinition], -) -> TokenStream { - let group_xml_block = |s: String, tag: &str| -> String { - if s.is_empty() { - s - } else { - format!("<{tag}>{s}") - } - }; - +) -> InherentImplXmlDocs { let signal_xml_elems = signals .iter() .filter_map(format_signal_xml) .collect::(); - let signals_block = group_xml_block(signal_xml_elems, "signals"); let constant_xml_elems = constants .iter() .map(|ConstDefinition { raw_constant }| raw_constant) .filter_map(format_constant_xml) .collect::(); - let constants_block = group_xml_block(constant_xml_elems, "constants"); let method_xml_elems = functions .iter() .filter_map(format_method_xml) .collect::(); - quote! { - ::godot::docs::InherentImplDocs { - methods: #method_xml_elems, - signals_block: #signals_block, - constants_block: #constants_block, - } + InherentImplXmlDocs { + method_xml_elems, + constant_xml_elems, + signal_xml_elems, } } diff --git a/godot-macros/src/docs/mod.rs b/godot-macros/src/docs/mod.rs new file mode 100644 index 000000000..f9bbbbc86 --- /dev/null +++ b/godot-macros/src/docs/mod.rs @@ -0,0 +1,115 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +#[cfg(all(feature = "register-docs", since_api = "4.3"))] +mod extract_docs; +#[cfg(all(feature = "register-docs", since_api = "4.3"))] +mod markdown_converter; + +use proc_macro2::{Ident, TokenStream}; + +use crate::class::{ConstDefinition, Field, FuncDefinition, SignalDefinition}; + +#[cfg(all(feature = "register-docs", since_api = "4.3"))] +mod docs_generators { + use quote::quote; + + use super::*; + + pub fn make_struct_docs_registration( + base: String, + description: &[venial::Attribute], + fields: &[Field], + class_name: &Ident, + prv: &TokenStream, + ) -> TokenStream { + let struct_docs = extract_docs::document_struct(base, description, fields); + quote! { + ::godot::sys::plugin_add!(#prv::__GODOT_DOCS_REGISTRY; #prv::DocsPlugin::new::<#class_name>( + #prv::DocsItem::Struct( + #struct_docs + ) + )); + } + } + + pub fn make_trait_docs_registration( + functions: &[FuncDefinition], + constants: &[ConstDefinition], + signals: &[SignalDefinition], + class_name: &Ident, + prv: &TokenStream, + ) -> TokenStream { + let extract_docs::InherentImplXmlDocs { + method_xml_elems, + constant_xml_elems, + signal_xml_elems, + } = extract_docs::document_inherent_impl(functions, constants, signals); + + quote! { + ::godot::sys::plugin_add!(#prv::__GODOT_DOCS_REGISTRY; #prv::DocsPlugin::new::<#class_name>( + #prv::DocsItem::InherentImpl(#prv::InherentImplDocs { + methods: #method_xml_elems, + signals: #signal_xml_elems, + constants: #constant_xml_elems + }) + )); + } + } + + pub fn make_interface_impl_docs_registration( + impl_members: &[venial::ImplMember], + class_name: &Ident, + prv: &TokenStream, + ) -> TokenStream { + let virtual_methods = extract_docs::document_interface_trait_impl(impl_members); + + quote! { + ::godot::sys::plugin_add!(#prv::__GODOT_DOCS_REGISTRY; #prv::DocsPlugin::new::<#class_name>( + #prv::DocsItem::ITraitImpl(#virtual_methods) + )); + } + } +} + +#[cfg(all(feature = "register-docs", since_api = "4.3"))] +pub use docs_generators::*; + +#[cfg(not(all(feature = "register-docs", since_api = "4.3")))] +mod placeholders { + use super::*; + + pub fn make_struct_docs_registration( + _base: String, + _description: &[venial::Attribute], + _fields: &[Field], + _class_name: &Ident, + _prv: &TokenStream, + ) -> TokenStream { + TokenStream::new() + } + + pub fn make_trait_docs_registration( + _functions: &[FuncDefinition], + _constants: &[ConstDefinition], + _signals: &[SignalDefinition], + _class_name: &Ident, + _prv: &proc_macro2::TokenStream, + ) -> TokenStream { + TokenStream::new() + } + + pub fn make_interface_impl_docs_registration( + _impl_members: &[venial::ImplMember], + _class_name: &Ident, + _prv: &TokenStream, + ) -> TokenStream { + TokenStream::new() + } +} + +#[cfg(not(all(feature = "register-docs", since_api = "4.3")))] +pub use placeholders::*; diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index 8ba7d92ae..0f35ff4d5 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -13,7 +13,6 @@ mod bench; mod class; mod derive; -#[cfg(all(feature = "register-docs", since_api = "4.3"))] mod docs; mod ffi_macros; mod gdextension; diff --git a/itest/rust/src/register_tests/constant_test.rs b/itest/rust/src/register_tests/constant_test.rs index ae8806403..9ab6f0066 100644 --- a/itest/rust/src/register_tests/constant_test.rs +++ b/itest/rust/src/register_tests/constant_test.rs @@ -172,8 +172,6 @@ godot::sys::plugin_add!( godot::private::ClassPlugin::new::( godot::private::PluginItem::InherentImpl( godot::private::InherentImpl::new::( - #[cfg(feature = "register-docs")] - Default::default() ) ) ) diff --git a/itest/rust/src/register_tests/register_docs_test.rs b/itest/rust/src/register_tests/register_docs_test.rs index a7951c656..d0c23037e 100644 --- a/itest/rust/src/register_tests/register_docs_test.rs +++ b/itest/rust/src/register_tests/register_docs_test.rs @@ -305,6 +305,20 @@ impl FairlyDocumented { fn other_signal(x: i64); } +#[godot_api(secondary)] +impl FairlyDocumented { + /// Documented method in godot_api secondary block + #[func] + fn secondary_but_documented(&self, _smth: i64) {} +} + +#[godot_api(secondary)] +impl FairlyDocumented { + /// Documented method in other godot_api secondary block + #[func] + fn trinary_but_documented(&self, _smth: i64) {} +} + #[itest] fn test_register_docs() { let xml = find_class_docs("FairlyDocumented"); diff --git a/itest/rust/src/register_tests/res/registered_docs.xml b/itest/rust/src/register_tests/res/registered_docs.xml index a0551a2ba..bf378abdb 100644 --- a/itest/rust/src/register_tests/res/registered_docs.xml +++ b/itest/rust/src/register_tests/res/registered_docs.xml @@ -17,6 +17,14 @@ public class Player : Node2D code&nbsp;block </div>[/codeblock][br][br][url=https://www.google.com/search?q=2+%2B+2+<+5]Google: 2 + 2 < 5[/url][br][br]connect these[br][br]¹ [url=https://example.org]https://example.org[/url][br]² because the glorb doesn't flibble.[br]³ This is the third footnote in order of definition.[br]⁴ Fourth footnote in order of definition.[br]⁵ This is the fifth footnote in order of definition.[br]⁶ sixth footnote in order of definition. + + + + + initialize this + + + @@ -65,11 +73,19 @@ public class Player : Node2D - - - + + + - initialize this + Documented method in godot_api secondary block + + + + + + + + Documented method in other godot_api secondary block