Browse Source

Merge pull request #1374 from dsnopek/gdext-docs

Allow submitting documentation to the Godot editor
Rémi Verschelde 1 year ago
parent
commit
17a82e7f94

+ 25 - 0
gdextension/gdextension_interface.h

@@ -2862,6 +2862,31 @@ typedef void (*GDExtensionInterfaceEditorAddPlugin)(GDExtensionConstStringNamePt
  */
  */
 typedef void (*GDExtensionInterfaceEditorRemovePlugin)(GDExtensionConstStringNamePtr p_class_name);
 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 a UTF-8 encoded C string (null terminated).
+ */
+typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8Chars)(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 a UTF-8 encoded C string.
+ * @param p_size The number of bytes (not code units).
+ */
+typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8CharsAndLen)(const char *p_data, GDExtensionInt p_size);
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 2 - 0
include/godot_cpp/core/method_bind.hpp

@@ -412,6 +412,7 @@ public:
 		method = p_method;
 		method = p_method;
 		generate_argument_types(sizeof...(P));
 		generate_argument_types(sizeof...(P));
 		set_argument_count(sizeof...(P));
 		set_argument_count(sizeof...(P));
+		set_const(true);
 	}
 	}
 };
 };
 
 
@@ -578,6 +579,7 @@ public:
 		generate_argument_types(sizeof...(P));
 		generate_argument_types(sizeof...(P));
 		set_argument_count(sizeof...(P));
 		set_argument_count(sizeof...(P));
 		set_return(true);
 		set_return(true);
+		set_const(true);
 	}
 	}
 };
 };
 
 

+ 7 - 0
include/godot_cpp/godot.hpp

@@ -192,6 +192,13 @@ extern "C" GDExtensionInterfaceClassdbUnregisterExtensionClass gdextension_inter
 extern "C" GDExtensionInterfaceGetLibraryPath gdextension_interface_get_library_path;
 extern "C" GDExtensionInterfaceGetLibraryPath gdextension_interface_get_library_path;
 extern "C" GDExtensionInterfaceEditorAddPlugin gdextension_interface_editor_add_plugin;
 extern "C" GDExtensionInterfaceEditorAddPlugin gdextension_interface_editor_add_plugin;
 extern "C" GDExtensionInterfaceEditorRemovePlugin gdextension_interface_editor_remove_plugin;
 extern "C" GDExtensionInterfaceEditorRemovePlugin gdextension_interface_editor_remove_plugin;
+extern "C" GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8Chars gdextension_interface_editor_help_load_xml_from_utf8_chars;
+extern "C" GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8CharsAndLen gdextension_interface_editor_help_load_xml_from_utf8_chars_and_len;
+
+class DocDataRegistration {
+public:
+	DocDataRegistration(const char *p_hash, int p_uncompressed_size, int p_compressed_size, const unsigned char *p_data);
+};
 
 
 } // namespace internal
 } // namespace internal
 
 

+ 52 - 0
src/godot.cpp

@@ -198,6 +198,38 @@ GDExtensionInterfaceClassdbUnregisterExtensionClass gdextension_interface_classd
 GDExtensionInterfaceGetLibraryPath gdextension_interface_get_library_path = nullptr;
 GDExtensionInterfaceGetLibraryPath gdextension_interface_get_library_path = nullptr;
 GDExtensionInterfaceEditorAddPlugin gdextension_interface_editor_add_plugin = nullptr;
 GDExtensionInterfaceEditorAddPlugin gdextension_interface_editor_add_plugin = nullptr;
 GDExtensionInterfaceEditorRemovePlugin gdextension_interface_editor_remove_plugin = nullptr;
 GDExtensionInterfaceEditorRemovePlugin gdextension_interface_editor_remove_plugin = nullptr;
+GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8Chars gdextension_interface_editor_help_load_xml_from_utf8_chars = nullptr;
+GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8CharsAndLen gdextension_interface_editor_help_load_xml_from_utf8_chars_and_len = nullptr;
+
+struct DocData {
+	const char *hash = nullptr;
+	int uncompressed_size = 0;
+	int compressed_size = 0;
+	const unsigned char *data = nullptr;
+
+	inline bool is_valid() const {
+		return hash != nullptr && uncompressed_size > 0 && compressed_size > 0 && data != nullptr;
+	}
+
+	void load_data() const;
+};
+
+static DocData &get_doc_data() {
+	static DocData doc_data;
+	return doc_data;
+}
+
+DocDataRegistration::DocDataRegistration(const char *p_hash, int p_uncompressed_size, int p_compressed_size, const unsigned char *p_data) {
+	DocData &doc_data = get_doc_data();
+	if (doc_data.is_valid()) {
+		printf("ERROR: Attempting to register documentation data when we already have some - discarding.\n");
+		return;
+	}
+	doc_data.hash = p_hash;
+	doc_data.uncompressed_size = p_uncompressed_size;
+	doc_data.compressed_size = p_compressed_size;
+	doc_data.data = p_data;
+}
 
 
 } // namespace internal
 } // namespace internal
 
 
@@ -440,6 +472,8 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
 	LOAD_PROC_ADDRESS(get_library_path, GDExtensionInterfaceGetLibraryPath);
 	LOAD_PROC_ADDRESS(get_library_path, GDExtensionInterfaceGetLibraryPath);
 	LOAD_PROC_ADDRESS(editor_add_plugin, GDExtensionInterfaceEditorAddPlugin);
 	LOAD_PROC_ADDRESS(editor_add_plugin, GDExtensionInterfaceEditorAddPlugin);
 	LOAD_PROC_ADDRESS(editor_remove_plugin, GDExtensionInterfaceEditorRemovePlugin);
 	LOAD_PROC_ADDRESS(editor_remove_plugin, GDExtensionInterfaceEditorRemovePlugin);
+	LOAD_PROC_ADDRESS(editor_help_load_xml_from_utf8_chars, GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8Chars);
+	LOAD_PROC_ADDRESS(editor_help_load_xml_from_utf8_chars_and_len, GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8CharsAndLen);
 
 
 	r_initialization->initialize = initialize_level;
 	r_initialization->initialize = initialize_level;
 	r_initialization->deinitialize = deinitialize_level;
 	r_initialization->deinitialize = deinitialize_level;
@@ -469,6 +503,13 @@ void GDExtensionBinding::initialize_level(void *p_userdata, GDExtensionInitializ
 		ClassDB::initialize(p_level);
 		ClassDB::initialize(p_level);
 	}
 	}
 	level_initialized[p_level]++;
 	level_initialized[p_level]++;
+
+	if ((ModuleInitializationLevel)p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
+		const internal::DocData &doc_data = internal::get_doc_data();
+		if (doc_data.is_valid()) {
+			doc_data.load_data();
+		}
+	}
 }
 }
 
 
 void GDExtensionBinding::deinitialize_level(void *p_userdata, GDExtensionInitializationLevel p_level) {
 void GDExtensionBinding::deinitialize_level(void *p_userdata, GDExtensionInitializationLevel p_level) {
@@ -535,4 +576,15 @@ GDExtensionBool GDExtensionBinding::InitObject::init() const {
 	return GDExtensionBinding::init(get_proc_address, library, init_data, initialization);
 	return GDExtensionBinding::init(get_proc_address, library, init_data, initialization);
 }
 }
 
 
+void internal::DocData::load_data() const {
+	PackedByteArray compressed;
+	compressed.resize(compressed_size);
+	memcpy(compressed.ptrw(), data, compressed_size);
+
+	// FileAccess::COMPRESSION_DEFLATE = 1
+	PackedByteArray decompressed = compressed.decompress(uncompressed_size, 1);
+
+	internal::gdextension_interface_editor_help_load_xml_from_utf8_chars_and_len(reinterpret_cast<const char *>(decompressed.ptr()), uncompressed_size);
+}
+
 } // namespace godot
 } // namespace godot

+ 4 - 0
test/SConstruct

@@ -16,6 +16,10 @@ env = SConscript("../SConstruct")
 env.Append(CPPPATH=["src/"])
 env.Append(CPPPATH=["src/"])
 sources = Glob("src/*.cpp")
 sources = Glob("src/*.cpp")
 
 
+if env["target"] in ["editor", "template_debug"]:
+    doc_data = env.GodotCPPDocData("src/gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml"))
+    sources.append(doc_data)
+
 if env["platform"] == "macos":
 if env["platform"] == "macos":
     library = env.SharedLibrary(
     library = env.SharedLibrary(
         "project/bin/libgdexample.{}.{}.framework/libgdexample.{}.{}".format(
         "project/bin/libgdexample.{}.{}.framework/libgdexample.{}.{}".format(

+ 25 - 0
test/doc_classes/Example.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="Example" inherits="Control" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
+	<brief_description>
+		A test control defined in GDExtension.
+	</brief_description>
+	<description>
+		A control used for the automated GDExtension tests.
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+		<method name="simple_func">
+			<return type="void" />
+			<description>
+				Tests a simple function call.
+			</description>
+		</method>
+	</methods>
+	<members>
+	</members>
+	<signals>
+	</signals>
+	<constants>
+	</constants>
+</class>

+ 1 - 1
test/project/project.godot

@@ -12,7 +12,7 @@ config_version=5
 
 
 config/name="GDExtension Test Project"
 config/name="GDExtension Test Project"
 run/main_scene="res://main.tscn"
 run/main_scene="res://main.tscn"
-config/features=PackedStringArray("4.2")
+config/features=PackedStringArray("4.3")
 config/icon="res://icon.png"
 config/icon="res://icon.png"
 
 
 [native_extensions]
 [native_extensions]

+ 47 - 1
tools/godotcpp.py

@@ -325,6 +325,51 @@ def options(opts, env):
             tool.options(opts)
             tool.options(opts)
 
 
 
 
+def make_doc_source(target, source, env):
+    import zlib
+
+    dst = str(target[0])
+    g = open(dst, "w", encoding="utf-8")
+    buf = ""
+    docbegin = ""
+    docend = ""
+    for src in source:
+        src_path = str(src)
+        if not src_path.endswith(".xml"):
+            continue
+        with open(src_path, "r", encoding="utf-8") as f:
+            content = f.read()
+        buf += content
+
+    buf = (docbegin + buf + docend).encode("utf-8")
+    decomp_size = len(buf)
+
+    # Use maximum zlib compression level to further reduce file size
+    # (at the cost of initial build times).
+    buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION)
+
+    g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
+    g.write("\n")
+    g.write("#include <godot_cpp/godot.hpp>\n")
+    g.write("\n")
+
+    g.write('static const char *_doc_data_hash = "' + str(hash(buf)) + '";\n')
+    g.write("static const int _doc_data_uncompressed_size = " + str(decomp_size) + ";\n")
+    g.write("static const int _doc_data_compressed_size = " + str(len(buf)) + ";\n")
+    g.write("static const unsigned char _doc_data_compressed[] = {\n")
+    for i in range(len(buf)):
+        g.write("\t" + str(buf[i]) + ",\n")
+    g.write("};\n")
+    g.write("\n")
+
+    g.write(
+        "static godot::internal::DocDataRegistration _doc_data_registration(_doc_data_hash, _doc_data_uncompressed_size, _doc_data_compressed_size, _doc_data_compressed);\n"
+    )
+    g.write("\n")
+
+    g.close()
+
+
 def generate(env):
 def generate(env):
     # Default num_jobs to local cpu count if not user specified.
     # Default num_jobs to local cpu count if not user specified.
     # SCons has a peculiarity where user-specified options won't be overridden
     # SCons has a peculiarity where user-specified options won't be overridden
@@ -451,7 +496,8 @@ def generate(env):
     # Builders
     # Builders
     env.Append(
     env.Append(
         BUILDERS={
         BUILDERS={
-            "GodotCPPBindings": Builder(action=Action(scons_generate_bindings, "$GENCOMSTR"), emitter=scons_emit_files)
+            "GodotCPPBindings": Builder(action=Action(scons_generate_bindings, "$GENCOMSTR"), emitter=scons_emit_files),
+            "GodotCPPDocData": Builder(action=make_doc_source),
         }
         }
     )
     )
     env.AddMethod(_godot_cpp, "GodotCPP")
     env.AddMethod(_godot_cpp, "GodotCPP")