From 24a69c51daa47eaadfecf71ee0ccb5291c0d569a Mon Sep 17 00:00:00 2001 From: Riteo Date: Thu, 19 Oct 2023 00:50:30 +0200 Subject: [PATCH] GDExtension: add an interface for loading extra documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two new GDExtension interface methods: - `editor_help_load_xml_from_utf8_chars` - `editor_help_load_xml_from_utf8_chars_and_len` Both of these methods parse the XML passed into an extra documentation container which, when needed, is merged into the main doc container. Co-Authored-By: RĂ©mi Verschelde --- core/extension/gdextension.cpp | 15 +++++++++ core/extension/gdextension.h | 20 ++++++++++++ core/extension/gdextension_interface.cpp | 17 ++++++++++ core/extension/gdextension_interface.h | 25 +++++++++++++++ editor/doc_tools.cpp | 12 +++++++ editor/doc_tools.h | 1 + editor/editor_help.cpp | 41 ++++++++++++++++++++++++ editor/editor_help.h | 6 ++++ editor/register_editor_types.cpp | 4 +++ 9 files changed, 141 insertions(+) diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 26512d0c562b..cf041e0c7eca 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -653,6 +653,8 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra if (!ext->is_reloading) { self->extension_classes.erase(class_name); } + + GDExtensionEditorHelp::remove_class(class_name); #else self->extension_classes.erase(class_name); #endif @@ -1183,4 +1185,17 @@ void GDExtensionEditorPlugins::remove_extension_class(const StringName &p_class_ extension_classes.erase(p_class_name); } } + +GDExtensionEditorHelp::EditorHelpLoadXmlBufferFunc GDExtensionEditorHelp::editor_help_load_xml_buffer = nullptr; +GDExtensionEditorHelp::EditorHelpRemoveClassFunc GDExtensionEditorHelp::editor_help_remove_class = nullptr; + +void GDExtensionEditorHelp::load_xml_buffer(const uint8_t *p_buffer, int p_size) { + ERR_FAIL_NULL(editor_help_load_xml_buffer); + editor_help_load_xml_buffer(p_buffer, p_size); +} + +void GDExtensionEditorHelp::remove_class(const String &p_class) { + ERR_FAIL_NULL(editor_help_remove_class); + editor_help_remove_class(p_class); +} #endif // TOOLS_ENABLED diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index bab3bcd1983d..1019e601fdbd 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -195,6 +195,26 @@ class GDExtensionEditorPlugins { return extension_classes; } }; + +class GDExtensionEditorHelp { +protected: + friend class EditorHelp; + + // Similarly to EditorNode above, we need to be able to ask EditorHelp to parse + // new documentation data. Note though that, differently from EditorHelp, this + // is initialized even _before_ it gets instantiated, as we need to rely on + // this method while initializing the engine. + typedef void (*EditorHelpLoadXmlBufferFunc)(const uint8_t *p_buffer, int p_size); + static EditorHelpLoadXmlBufferFunc editor_help_load_xml_buffer; + + typedef void (*EditorHelpRemoveClassFunc)(const String &p_class); + static EditorHelpRemoveClassFunc editor_help_remove_class; + +public: + static void load_xml_buffer(const uint8_t *p_buffer, int p_size); + static void remove_class(const String &p_class); +}; + #endif // TOOLS_ENABLED #endif // GDEXTENSION_H diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index d1b4e6a30f2e..36733880088c 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -42,6 +42,8 @@ #include "core/variant/variant.h" #include "core/version.h" +#include + class CallableCustomExtension : public CallableCustom { void *userdata; void *token; @@ -1375,6 +1377,19 @@ static void gdextension_editor_remove_plugin(GDExtensionConstStringNamePtr p_cla #endif } +static void gdextension_editor_help_load_xml_from_utf8_chars_and_len(const char *p_data, GDExtensionInt p_size) { +#ifdef TOOLS_ENABLED + GDExtensionEditorHelp::load_xml_buffer((const uint8_t *)p_data, p_size); +#endif +} + +static void gdextension_editor_help_load_xml_from_utf8_chars(const char *p_data) { +#ifdef TOOLS_ENABLED + size_t len = strlen(p_data); + gdextension_editor_help_load_xml_from_utf8_chars_and_len(p_data, len); +#endif +} + #define REGISTER_INTERFACE_FUNC(m_name) GDExtension::register_interface_function(#m_name, (GDExtensionInterfaceFunctionPtr)&gdextension_##m_name) void gdextension_setup_interface() { @@ -1518,6 +1533,8 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(classdb_get_class_tag); REGISTER_INTERFACE_FUNC(editor_add_plugin); REGISTER_INTERFACE_FUNC(editor_remove_plugin); + REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars); + REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars_and_len); } #undef REGISTER_INTERFACE_FUNCTION diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index 240da6e07382..b2520adfe864 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -2617,6 +2617,31 @@ typedef void (*GDExtensionInterfaceEditorAddPlugin)(GDExtensionConstStringNamePt */ typedef void (*GDExtensionInterfaceEditorRemovePlugin)(GDExtensionConstStringNamePtr p_class_name); +/** + * @name editor_help_load_xml_from_utf8_chars + * @since 4.3 + * + * Loads new XML-formatted documentation data in the editor. + * + * The provided pointer can be immediately freed once the function returns. + * + * @param p_data A pointer to an UTF-8 encoded C string (null terminated). + */ +typedef void (*GDExtensionsInterfaceEditorHelpLoadXML)(const char *p_data); + +/** + * @name editor_help_load_xml_from_utf8_chars_and_len + * @since 4.3 + * + * Loads new XML-formatted documentation data in the editor. + * + * The provided pointer can be immediately freed once the function returns. + * + * @param p_data A pointer to an UTF-8 encoded C string. + * @param p_size The number of bytes (not code units). + */ +typedef void (*GDExtensionsInterfaceEditorHelpLoadXMLAndLen)(const char *p_data, GDExtensionInt p_size); + #ifdef __cplusplus } #endif diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index ef3981fd3f64..75b6f035384f 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -1635,3 +1635,15 @@ Error DocTools::load_compressed(const uint8_t *p_data, int p_compressed_size, in return OK; } + +Error DocTools::load_xml(const uint8_t *p_data, int p_size) { + Ref parser = memnew(XMLParser); + Error err = parser->_open_buffer(p_data, p_size); + if (err) { + return err; + } + + _load(parser); + + return OK; +} diff --git a/editor/doc_tools.h b/editor/doc_tools.h index 2d4a45bda008..a453b1b01870 100644 --- a/editor/doc_tools.h +++ b/editor/doc_tools.h @@ -51,6 +51,7 @@ class DocTools { Error _load(Ref parser); Error load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size); + Error load_xml(const uint8_t *p_data, int p_size); }; #endif // DOC_TOOLS_H diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index d6eadf3ab4ff..f31efe634f43 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -31,6 +31,7 @@ #include "editor_help.h" #include "core/core_constants.h" +#include "core/extension/gdextension.h" #include "core/input/input.h" #include "core/object/script_language.h" #include "core/os/keyboard.h" @@ -83,6 +84,7 @@ const Vector classes_with_csharp_differences = { // TODO: this is sometimes used directly as doc->something, other times as EditorHelp::get_doc_data(), which is thread-safe. // Might this be a problem? DocTools *EditorHelp::doc = nullptr; +DocTools *EditorHelp::ext_doc = nullptr; static bool _attempt_doc_load(const String &p_class) { // Docgen always happens in the outer-most class: it also generates docs for inner classes. @@ -2372,6 +2374,28 @@ String EditorHelp::get_cache_full_path() { return EditorPaths::get_singleton()->get_cache_dir().path_join("editor_doc_cache.res"); } +void EditorHelp::load_xml_buffer(const uint8_t *p_buffer, int p_size) { + if (!ext_doc) { + ext_doc = memnew(DocTools); + } + + ext_doc->load_xml(p_buffer, p_size); + + if (doc) { + doc->load_xml(p_buffer, p_size); + } +} + +void EditorHelp::remove_class(const String &p_class) { + if (ext_doc && ext_doc->has_doc(p_class)) { + ext_doc->remove_doc(p_class); + } + + if (doc && doc->has_doc(p_class)) { + doc->remove_doc(p_class); + } +} + static bool first_attempt = true; static String _compute_doc_version_hash() { @@ -2392,6 +2416,12 @@ void EditorHelp::_load_doc_thread(void *p_udata) { first_attempt = false; callable_mp_static(&EditorHelp::generate_doc).bind(true).call_deferred(); } + + // Append extra doc data. This is needed because it would otherwise be wiped by + // the doc regeneration routine. + if (ext_doc) { + doc->merge_from(*ext_doc); + } } void EditorHelp::_gen_doc_thread(void *p_udata) { @@ -2411,6 +2441,12 @@ void EditorHelp::_gen_doc_thread(void *p_udata) { if (err) { ERR_PRINT("Cannot save editor help cache (" + get_cache_full_path() + ")."); } + + // Append extra doc data. This is needed because it would otherwise be wiped by + // the doc regeneration routine. + if (ext_doc) { + doc->merge_from(*ext_doc); + } } static bool doc_gen_use_threads = true; @@ -2561,6 +2597,11 @@ void EditorHelp::_bind_methods() { ADD_SIGNAL(MethodInfo("go_to_help")); } +void EditorHelp::init_gdext_pointers() { + GDExtensionEditorHelp::editor_help_load_xml_buffer = &EditorHelp::load_xml_buffer; + GDExtensionEditorHelp::editor_help_remove_class = &EditorHelp::remove_class; +} + EditorHelp::EditorHelp() { set_custom_minimum_size(Size2(150 * EDSCALE, 0)); diff --git a/editor/editor_help.h b/editor/editor_help.h index 0ca3942e0bea..487c591ef372 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -113,6 +113,7 @@ class EditorHelp : public VBoxContainer { RichTextLabel *class_desc = nullptr; HSplitContainer *h_split = nullptr; static DocTools *doc; + static DocTools *ext_doc; ConfirmationDialog *search_dialog = nullptr; LineEdit *search = nullptr; @@ -206,6 +207,9 @@ class EditorHelp : public VBoxContainer { static void cleanup_doc(); static String get_cache_full_path(); + static void load_xml_buffer(const uint8_t *p_buffer, int p_size); + static void remove_class(const String &p_class); + void go_to_help(const String &p_help); void go_to_class(const String &p_class); void update_doc(); @@ -225,6 +229,8 @@ class EditorHelp : public VBoxContainer { void update_toggle_scripts_button(); + static void init_gdext_pointers(); + EditorHelp(); ~EditorHelp(); }; diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 9a667d57758d..14d5ec478081 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -282,6 +282,10 @@ void register_editor_types() { ei_singleton.editor_only = true; Engine::get_singleton()->add_singleton(ei_singleton); + // Required as GDExtensions can register docs at init time way before this + // class is actually instantiated. + EditorHelp::init_gdext_pointers(); + OS::get_singleton()->benchmark_end_measure("register_editor_types"); }