Skip to content

Commit

Permalink
Allow submitting documentation to the Godot editor
Browse files Browse the repository at this point in the history
  • Loading branch information
dsnopek committed May 3, 2024
1 parent 54fe2f9 commit 5baf2aa
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 2 deletions.
25 changes: 25 additions & 0 deletions gdextension/gdextension_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -2835,6 +2835,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 (*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 an 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
}
#endif
Expand Down
8 changes: 8 additions & 0 deletions include/godot_cpp/godot.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ extern "C" GDExtensionInterfaceClassdbUnregisterExtensionClass gdextension_inter
extern "C" GDExtensionInterfaceGetLibraryPath gdextension_interface_get_library_path;
extern "C" GDExtensionInterfaceEditorAddPlugin gdextension_interface_editor_add_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

Expand Down
52 changes: 52 additions & 0 deletions src/godot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,38 @@ GDExtensionInterfaceClassdbUnregisterExtensionClass gdextension_interface_classd
GDExtensionInterfaceGetLibraryPath gdextension_interface_get_library_path = nullptr;
GDExtensionInterfaceEditorAddPlugin gdextension_interface_editor_add_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

Expand Down Expand Up @@ -436,6 +468,8 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
LOAD_PROC_ADDRESS(get_library_path, GDExtensionInterfaceGetLibraryPath);
LOAD_PROC_ADDRESS(editor_add_plugin, GDExtensionInterfaceEditorAddPlugin);
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->deinitialize = deinitialize_level;
Expand Down Expand Up @@ -465,6 +499,13 @@ void GDExtensionBinding::initialize_level(void *p_userdata, GDExtensionInitializ
ClassDB::initialize(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) {
Expand Down Expand Up @@ -531,4 +572,15 @@ GDExtensionBool GDExtensionBinding::InitObject::init() const {
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
3 changes: 3 additions & 0 deletions test/SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ env = SConscript("../SConstruct")
env.Append(CPPPATH=["src/"])
sources = Glob("src/*.cpp")

doc_data = env.GodotCPPDocData("src/doc_data.gen.cpp", source=Glob("doc_classes/*.xml"))
sources.append(doc_data)

if env["platform"] == "macos":
library = env.SharedLibrary(
"project/bin/libgdexample.{}.{}.framework/libgdexample.{}.{}".format(
Expand Down
25 changes: 25 additions & 0 deletions test/doc_classes/Example.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="Example" inherits="Control">
<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>
2 changes: 1 addition & 1 deletion test/project/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ config_version=5

config/name="GDExtension Test Project"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.2")
config/features=PackedStringArray("4.3")
config/icon="res://icon.png"

[native_extensions]
Expand Down
48 changes: 47 additions & 1 deletion tools/godotcpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,51 @@ def options(opts, env):
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):
# Default num_jobs to local cpu count if not user specified.
# SCons has a peculiarity where user-specified options won't be overridden
Expand Down Expand Up @@ -451,7 +496,8 @@ def generate(env):
# Builders
env.Append(
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")
Expand Down

0 comments on commit 5baf2aa

Please sign in to comment.