You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-12-02 16:48:55 +00:00
GDExtension: Store source of gdextension_interface.h in JSON
This commit is contained in:
4
.github/workflows/static_checks.yml
vendored
4
.github/workflows/static_checks.yml
vendored
@@ -41,7 +41,3 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install libxml2-utils
|
sudo apt-get install libxml2-utils
|
||||||
xmllint --quiet --noout --schema doc/class.xsd doc/classes/*.xml modules/*/doc_classes/*.xml platform/*/doc_classes/*.xml
|
xmllint --quiet --noout --schema doc/class.xsd doc/classes/*.xml modules/*/doc_classes/*.xml platform/*/doc_classes/*.xml
|
||||||
|
|
||||||
- name: Run C compiler on `gdextension_interface.h`
|
|
||||||
run: |
|
|
||||||
gcc -c core/extension/gdextension_interface.h
|
|
||||||
|
|||||||
@@ -57,6 +57,13 @@ repos:
|
|||||||
- id: codespell
|
- id: codespell
|
||||||
additional_dependencies: [tomli]
|
additional_dependencies: [tomli]
|
||||||
|
|
||||||
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||||
|
rev: 0.34.1
|
||||||
|
hooks:
|
||||||
|
- id: check-jsonschema
|
||||||
|
files: ^core/extension/gdextension_interface\.json$
|
||||||
|
args: ["--schemafile", "core/extension/gdextension_interface.schema.json"]
|
||||||
|
|
||||||
### Requires Docker; look into alternative implementation.
|
### Requires Docker; look into alternative implementation.
|
||||||
# - repo: https://github.com/comkieffer/pre-commit-xmllint.git
|
# - repo: https://github.com/comkieffer/pre-commit-xmllint.git
|
||||||
# rev: 1.0.0
|
# rev: 1.0.0
|
||||||
|
|||||||
@@ -4,14 +4,20 @@ from misc.utility.scons_hints import *
|
|||||||
Import("env")
|
Import("env")
|
||||||
|
|
||||||
import make_interface_dumper
|
import make_interface_dumper
|
||||||
|
import make_interface_header
|
||||||
import make_wrappers
|
import make_wrappers
|
||||||
|
|
||||||
env.CommandNoCache(["ext_wrappers.gen.inc"], "make_wrappers.py", env.Run(make_wrappers.run))
|
env.CommandNoCache(["ext_wrappers.gen.inc"], "make_wrappers.py", env.Run(make_wrappers.run))
|
||||||
env.CommandNoCache(
|
env.CommandNoCache(
|
||||||
"gdextension_interface_dump.gen.h",
|
"gdextension_interface_dump.gen.h",
|
||||||
["gdextension_interface.h", "make_interface_dumper.py"],
|
["gdextension_interface.json", "make_interface_dumper.py"],
|
||||||
env.Run(make_interface_dumper.run),
|
env.Run(make_interface_dumper.run),
|
||||||
)
|
)
|
||||||
|
env.CommandNoCache(
|
||||||
|
"gdextension_interface.gen.h",
|
||||||
|
["gdextension_interface.json", "make_interface_header.py"],
|
||||||
|
env.Run(make_interface_header.run),
|
||||||
|
)
|
||||||
|
|
||||||
env_extension = env.Clone()
|
env_extension = env.Clone()
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/extension/gdextension_interface.h"
|
#include "core/extension/gdextension_interface.gen.h"
|
||||||
#include "core/extension/gdextension_loader.h"
|
#include "core/extension/gdextension_loader.h"
|
||||||
#include "core/io/config_file.h"
|
#include "core/io/config_file.h"
|
||||||
#include "core/io/resource_loader.h"
|
#include "core/io/resource_loader.h"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
|
|
||||||
#include "gdextension_interface.h"
|
#include "gdextension_interface.gen.h"
|
||||||
|
|
||||||
#include "core/config/engine.h"
|
#include "core/config/engine.h"
|
||||||
#include "core/extension/gdextension.h"
|
#include "core/extension/gdextension.h"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
9359
core/extension/gdextension_interface.json
Normal file
9359
core/extension/gdextension_interface.json
Normal file
File diff suppressed because it is too large
Load Diff
290
core/extension/gdextension_interface.schema.json
Normal file
290
core/extension/gdextension_interface.schema.json
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://godotengine.org/gdextension_interface.schema.json",
|
||||||
|
"title": "GDExtension Interface",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"_copyright": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"format_version": {
|
||||||
|
"type": "number",
|
||||||
|
"const": 1
|
||||||
|
},
|
||||||
|
"$schema": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "./gdextension_interface.schema.json"
|
||||||
|
},
|
||||||
|
"types": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["enum", "handle", "alias", "struct", "function"]
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"kind"
|
||||||
|
],
|
||||||
|
"unevaluatedProperties": false,
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "enum" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"values": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [ "name", "value" ],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "kind", "values"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "handle" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"parent": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"const": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "alias" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "kind", "type"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "struct" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"members": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "type"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "kind", "members"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "function" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"return_value": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["type"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"arguments": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["type"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "kind", "return_value", "arguments"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"return_value": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["type"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"arguments": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["type"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"since": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "4\\.[0-9]+"
|
||||||
|
},
|
||||||
|
"deprecated": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"see": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"legacy_type_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"return_value",
|
||||||
|
"arguments",
|
||||||
|
"description",
|
||||||
|
"since"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"_copyright",
|
||||||
|
"$schema",
|
||||||
|
"format_version",
|
||||||
|
"types",
|
||||||
|
"interface"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
309
core/extension/gdextension_interface_header_generator.cpp
Normal file
309
core/extension/gdextension_interface_header_generator.cpp
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
/**************************************************************************/
|
||||||
|
/* gdextension_interface_header_generator.cpp */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||||
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
|
||||||
|
#include "gdextension_interface_header_generator.h"
|
||||||
|
|
||||||
|
#include "core/io/json.h"
|
||||||
|
#include "gdextension_interface_dump.gen.h"
|
||||||
|
|
||||||
|
static const char *FILE_HEADER =
|
||||||
|
"/**************************************************************************/\n"
|
||||||
|
"/* gdextension_interface.h */\n";
|
||||||
|
|
||||||
|
static const char *INTRO =
|
||||||
|
"\n"
|
||||||
|
"#pragma once\n"
|
||||||
|
"\n"
|
||||||
|
"/* This is a C class header, you can copy it and use it directly in your own binders.\n"
|
||||||
|
" * Together with the `extension_api.json` file, you should be able to generate any binder.\n"
|
||||||
|
" */\n"
|
||||||
|
"\n"
|
||||||
|
"#ifndef __cplusplus\n"
|
||||||
|
"#include <stddef.h>\n"
|
||||||
|
"#include <stdint.h>\n"
|
||||||
|
"\n"
|
||||||
|
"typedef uint32_t char32_t;\n"
|
||||||
|
"typedef uint16_t char16_t;\n"
|
||||||
|
"#else\n"
|
||||||
|
"#include <cstddef>\n"
|
||||||
|
"#include <cstdint>\n"
|
||||||
|
"\n"
|
||||||
|
"extern \"C\" {\n"
|
||||||
|
"#endif\n"
|
||||||
|
"\n";
|
||||||
|
|
||||||
|
static const char *OUTRO =
|
||||||
|
"#ifdef __cplusplus\n"
|
||||||
|
"}\n"
|
||||||
|
"#endif\n";
|
||||||
|
|
||||||
|
void GDExtensionInterfaceHeaderGenerator::generate_gdextension_interface_header(const String &p_path) {
|
||||||
|
Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
|
||||||
|
ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path));
|
||||||
|
|
||||||
|
Vector<uint8_t> bytes = GDExtensionInterfaceDump::load_gdextension_interface_file();
|
||||||
|
String json_string = String::utf8(Span<char>((char *)bytes.ptr(), bytes.size()));
|
||||||
|
|
||||||
|
Ref<JSON> json;
|
||||||
|
json.instantiate();
|
||||||
|
Error err = json->parse(json_string);
|
||||||
|
ERR_FAIL_COND(err);
|
||||||
|
|
||||||
|
Dictionary data = json->get_data();
|
||||||
|
ERR_FAIL_COND(data.is_empty());
|
||||||
|
|
||||||
|
fa->store_string(FILE_HEADER);
|
||||||
|
|
||||||
|
Array copyright = data["_copyright"];
|
||||||
|
for (const Variant &line : copyright) {
|
||||||
|
fa->store_line(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
fa->store_string(INTRO);
|
||||||
|
|
||||||
|
Array types = data["types"];
|
||||||
|
for (Dictionary type_dict : types) {
|
||||||
|
if (type_dict.has("description")) {
|
||||||
|
write_doc(fa, type_dict["description"]);
|
||||||
|
}
|
||||||
|
String kind = type_dict["kind"];
|
||||||
|
if (kind == "handle") {
|
||||||
|
type_dict["type"] = type_dict.get("const", false) ? "const void*" : "void*";
|
||||||
|
write_simple_type(fa, type_dict);
|
||||||
|
} else if (kind == "alias") {
|
||||||
|
write_simple_type(fa, type_dict);
|
||||||
|
} else if (kind == "enum") {
|
||||||
|
write_enum_type(fa, type_dict);
|
||||||
|
} else if (kind == "function") {
|
||||||
|
write_function_type(fa, type_dict);
|
||||||
|
} else if (kind == "struct") {
|
||||||
|
write_struct_type(fa, type_dict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Array interfaces = data["interface"];
|
||||||
|
for (const Variant &interface : interfaces) {
|
||||||
|
write_interface(fa, interface);
|
||||||
|
}
|
||||||
|
|
||||||
|
fa->store_string(OUTRO);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDExtensionInterfaceHeaderGenerator::write_doc(const Ref<FileAccess> &p_fa, const Array &p_doc, const String &p_indent) {
|
||||||
|
if (p_doc.size() == 1) {
|
||||||
|
p_fa->store_string(vformat("%s/* %s */\n", p_indent, p_doc[0]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
for (const Variant &line : p_doc) {
|
||||||
|
if (first) {
|
||||||
|
p_fa->store_string(p_indent + "/*");
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
p_fa->store_string(p_indent + " *");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line == "") {
|
||||||
|
p_fa->store_string("\n");
|
||||||
|
} else {
|
||||||
|
p_fa->store_line(String(" ") + (String)line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p_fa->store_string(p_indent + " */\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDExtensionInterfaceHeaderGenerator::write_simple_type(const Ref<FileAccess> &p_fa, const Dictionary &p_type) {
|
||||||
|
String type_and_name = format_type_and_name(p_type["type"], p_type["name"]);
|
||||||
|
p_fa->store_string(vformat("typedef %s;%s\n", type_and_name, make_deprecated_note(p_type)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDExtensionInterfaceHeaderGenerator::write_enum_type(const Ref<FileAccess> &p_fa, const Dictionary &p_enum) {
|
||||||
|
p_fa->store_string("typedef enum {\n");
|
||||||
|
Array values = p_enum["values"];
|
||||||
|
for (Dictionary value_dict : values) {
|
||||||
|
if (value_dict.has("description")) {
|
||||||
|
write_doc(p_fa, value_dict["description"], "\t");
|
||||||
|
}
|
||||||
|
p_fa->store_string(vformat("\t%s = %s,\n", value_dict["name"], (int)value_dict["value"]));
|
||||||
|
}
|
||||||
|
p_fa->store_string(vformat("} %s;%s\n\n", p_enum["name"], make_deprecated_note(p_enum)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDExtensionInterfaceHeaderGenerator::write_function_type(const Ref<FileAccess> &p_fa, const Dictionary &p_func) {
|
||||||
|
String args_text = p_func.has("arguments") ? make_args_text(p_func["arguments"]) : "";
|
||||||
|
String name_and_args = vformat("(*%s)(%s)", p_func["name"], args_text);
|
||||||
|
Dictionary ret = p_func["return_value"];
|
||||||
|
p_fa->store_string(vformat("typedef %s;%s\n", format_type_and_name(ret["type"], name_and_args), make_deprecated_note(p_func)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDExtensionInterfaceHeaderGenerator::write_struct_type(const Ref<FileAccess> &p_fa, const Dictionary &p_struct) {
|
||||||
|
p_fa->store_string("typedef struct {\n");
|
||||||
|
Array members = p_struct["members"];
|
||||||
|
for (Dictionary member_dict : members) {
|
||||||
|
if (member_dict.has("description")) {
|
||||||
|
write_doc(p_fa, member_dict["description"], "\t");
|
||||||
|
}
|
||||||
|
p_fa->store_string(vformat("\t%s;\n", format_type_and_name(member_dict["type"], member_dict["name"])));
|
||||||
|
}
|
||||||
|
p_fa->store_string(vformat("} %s;%s\n\n", p_struct["name"], make_deprecated_note(p_struct)));
|
||||||
|
}
|
||||||
|
|
||||||
|
String GDExtensionInterfaceHeaderGenerator::format_type_and_name(const String &p_type, const String &p_name) {
|
||||||
|
String ret = p_type;
|
||||||
|
bool is_pointer = false;
|
||||||
|
if (ret.ends_with("*")) {
|
||||||
|
ret = ret.substr(0, ret.size() - 2) + " *";
|
||||||
|
is_pointer = true;
|
||||||
|
}
|
||||||
|
if (!p_name.is_empty()) {
|
||||||
|
if (is_pointer) {
|
||||||
|
ret = ret + p_name;
|
||||||
|
} else {
|
||||||
|
ret = ret + " " + p_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
String GDExtensionInterfaceHeaderGenerator::make_deprecated_note(const Dictionary &p_type) {
|
||||||
|
if (!p_type.has("deprecated")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return vformat(" /* %s */", p_type["deprecated"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
String GDExtensionInterfaceHeaderGenerator::make_args_text(const Array &p_args) {
|
||||||
|
Vector<String> combined;
|
||||||
|
for (Dictionary arg_dict : p_args) {
|
||||||
|
combined.push_back(format_type_and_name(arg_dict["type"], arg_dict.get("name", String())));
|
||||||
|
}
|
||||||
|
return String(", ").join(combined);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDExtensionInterfaceHeaderGenerator::write_interface(const Ref<FileAccess> &p_fa, const Dictionary &p_interface) {
|
||||||
|
Vector<String> doc;
|
||||||
|
|
||||||
|
doc.push_back(String("@name ") + (String)p_interface["name"]);
|
||||||
|
doc.push_back(String("@since ") + (String)p_interface["since"]);
|
||||||
|
|
||||||
|
if (p_interface.has("deprecated")) {
|
||||||
|
String deprecated = p_interface["deprecated"];
|
||||||
|
if (deprecated.to_lower().begins_with("deprecated")) {
|
||||||
|
Vector<String> parts = deprecated.split_spaces(1);
|
||||||
|
deprecated = parts[1];
|
||||||
|
}
|
||||||
|
doc.push_back(String("@deprecated ") + deprecated);
|
||||||
|
}
|
||||||
|
|
||||||
|
Array orig_doc = p_interface["description"];
|
||||||
|
for (int i = 0; i < orig_doc.size(); i++) {
|
||||||
|
// Put an empty line before the 1st and 2nd lines.
|
||||||
|
if (i <= 1) {
|
||||||
|
doc.push_back("");
|
||||||
|
}
|
||||||
|
doc.push_back(orig_doc[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_interface.has("arguments")) {
|
||||||
|
Array args = p_interface["arguments"];
|
||||||
|
if (args.size() > 0) {
|
||||||
|
doc.push_back("");
|
||||||
|
for (Dictionary arg_dict : args) {
|
||||||
|
String arg_string = String("@param ") + (String)arg_dict["name"];
|
||||||
|
if (arg_dict.has("description")) {
|
||||||
|
Array arg_doc = arg_dict["description"];
|
||||||
|
for (const Variant &d : arg_doc) {
|
||||||
|
arg_string += String(" ") + (String)d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doc.push_back(arg_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_interface.has("return_value")) {
|
||||||
|
Dictionary ret = p_interface["return_value"];
|
||||||
|
if (ret["type"] != "void") {
|
||||||
|
String ret_string = String("@return");
|
||||||
|
if (ret.has("description")) {
|
||||||
|
Array arg_doc = ret["description"];
|
||||||
|
for (const Variant &d : arg_doc) {
|
||||||
|
ret_string += String(" ") + (String)d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doc.push_back("");
|
||||||
|
doc.push_back(ret_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_interface.has("see")) {
|
||||||
|
Array see_array = p_interface["see"];
|
||||||
|
if (see_array.size() > 0) {
|
||||||
|
doc.push_back("");
|
||||||
|
for (const Variant &see : see_array) {
|
||||||
|
doc.push_back(String("@see ") + (String)see);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p_fa->store_string("/**\n");
|
||||||
|
for (const String &d : doc) {
|
||||||
|
if (d == "") {
|
||||||
|
p_fa->store_string(" *\n");
|
||||||
|
} else {
|
||||||
|
p_fa->store_string(vformat(" * %s\n", d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p_fa->store_string(" */\n");
|
||||||
|
|
||||||
|
Dictionary func = p_interface.duplicate();
|
||||||
|
func.erase("deprecated");
|
||||||
|
if (p_interface.has("legacy_type_name")) {
|
||||||
|
// @todo When we can break compat, remove this! This maintains legacy type-o's in some type names.
|
||||||
|
func["name"] = p_interface["legacy_type_name"];
|
||||||
|
} else {
|
||||||
|
// Cannot use `to_pascal_case()` because it'll capitalize after numbers.
|
||||||
|
Vector<String> words = ((String)p_interface["name"]).split("_");
|
||||||
|
for (String &word : words) {
|
||||||
|
// Cannot use `capitalize()` on the whole string, because it'll separate numbers with a space.
|
||||||
|
word[0] = String::char_uppercase(word[0]);
|
||||||
|
}
|
||||||
|
func["name"] = String("GDExtensionInterface") + String().join(words);
|
||||||
|
}
|
||||||
|
write_function_type(p_fa, func);
|
||||||
|
|
||||||
|
p_fa->store_string("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // TOOLS_ENABLED
|
||||||
55
core/extension/gdextension_interface_header_generator.h
Normal file
55
core/extension/gdextension_interface_header_generator.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/**************************************************************************/
|
||||||
|
/* gdextension_interface_header_generator.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||||
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
|
||||||
|
#include "core/io/file_access.h"
|
||||||
|
|
||||||
|
class GDExtensionInterfaceHeaderGenerator {
|
||||||
|
public:
|
||||||
|
static void generate_gdextension_interface_header(const String &p_path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void write_doc(const Ref<FileAccess> &p_fa, const Array &p_doc, const String &p_indent = "");
|
||||||
|
static void write_simple_type(const Ref<FileAccess> &p_fa, const Dictionary &p_type);
|
||||||
|
static void write_enum_type(const Ref<FileAccess> &p_fa, const Dictionary &p_enum);
|
||||||
|
static void write_function_type(const Ref<FileAccess> &p_fa, const Dictionary &p_func);
|
||||||
|
static void write_struct_type(const Ref<FileAccess> &p_fa, const Dictionary &p_struct);
|
||||||
|
|
||||||
|
static String format_type_and_name(const String &p_type, const String &p_name);
|
||||||
|
static String make_deprecated_note(const Dictionary &p_type);
|
||||||
|
static String make_args_text(const Array &p_args);
|
||||||
|
|
||||||
|
static void write_interface(const Ref<FileAccess> &p_fa, const Dictionary &p_interface);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TOOLS_ENABLED
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/extension/gdextension_interface.h"
|
#include "core/extension/gdextension_interface.gen.h"
|
||||||
#include "core/object/class_db.h"
|
#include "core/object/class_db.h"
|
||||||
|
|
||||||
class GodotInstance : public Object {
|
class GodotInstance : public Object {
|
||||||
|
|||||||
@@ -22,15 +22,22 @@ inline constexpr unsigned char _gdextension_interface_data_compressed[] = {{
|
|||||||
|
|
||||||
class GDExtensionInterfaceDump {{
|
class GDExtensionInterfaceDump {{
|
||||||
public:
|
public:
|
||||||
static void generate_gdextension_interface_file(const String &p_path) {{
|
static Vector<uint8_t> load_gdextension_interface_file() {{
|
||||||
Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
|
|
||||||
ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path));
|
|
||||||
Vector<uint8_t> data;
|
Vector<uint8_t> data;
|
||||||
data.resize(_gdextension_interface_data_uncompressed_size);
|
data.resize(_gdextension_interface_data_uncompressed_size);
|
||||||
int ret = Compression::decompress(data.ptrw(), _gdextension_interface_data_uncompressed_size, _gdextension_interface_data_compressed, _gdextension_interface_data_compressed_size, Compression::MODE_DEFLATE);
|
int ret = Compression::decompress(data.ptrw(), _gdextension_interface_data_uncompressed_size, _gdextension_interface_data_compressed, _gdextension_interface_data_compressed_size, Compression::MODE_DEFLATE);
|
||||||
ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");
|
ERR_FAIL_COND_V_MSG(ret == -1, Vector<uint8_t>(), "Compressed file is corrupt.");
|
||||||
|
return data;
|
||||||
|
}}
|
||||||
|
|
||||||
|
static void generate_gdextension_interface_file(const String &p_path) {{
|
||||||
|
Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
|
||||||
|
ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path));
|
||||||
|
Vector<uint8_t> data = load_gdextension_interface_file();
|
||||||
|
if (data.size() > 0) {{
|
||||||
fa->store_buffer(data.ptr(), data.size());
|
fa->store_buffer(data.ptr(), data.size());
|
||||||
}};
|
}}
|
||||||
|
}}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
#endif // TOOLS_ENABLED
|
#endif // TOOLS_ENABLED
|
||||||
|
|||||||
306
core/extension/make_interface_header.py
Normal file
306
core/extension/make_interface_header.py
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
import difflib
|
||||||
|
import json
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
import methods
|
||||||
|
|
||||||
|
BASE_TYPES = [
|
||||||
|
"void",
|
||||||
|
"int",
|
||||||
|
"int8_t",
|
||||||
|
"uint8_t",
|
||||||
|
"int16_t",
|
||||||
|
"uint16_t",
|
||||||
|
"int32_t",
|
||||||
|
"uint32_t",
|
||||||
|
"int64_t",
|
||||||
|
"uint64_t",
|
||||||
|
"size_t",
|
||||||
|
"char",
|
||||||
|
"char16_t",
|
||||||
|
"char32_t",
|
||||||
|
"wchar_t",
|
||||||
|
"float",
|
||||||
|
"double",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def run(target, source, env):
|
||||||
|
filename = str(source[0])
|
||||||
|
buffer = methods.get_buffer(filename)
|
||||||
|
data = json.loads(buffer, object_pairs_hook=OrderedDict)
|
||||||
|
check_formatting(buffer.decode("utf-8"), data, filename)
|
||||||
|
check_allowed_keys(data, ["_copyright", "$schema", "format_version", "types", "interface"])
|
||||||
|
|
||||||
|
valid_data_types = {}
|
||||||
|
for type in BASE_TYPES:
|
||||||
|
valid_data_types[type] = True
|
||||||
|
|
||||||
|
with methods.generated_wrapper(str(target[0])) as file:
|
||||||
|
file.write("""\
|
||||||
|
#ifndef __cplusplus
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef uint32_t char32_t;
|
||||||
|
typedef uint16_t char16_t;
|
||||||
|
#else
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
""")
|
||||||
|
|
||||||
|
handles = []
|
||||||
|
for type in data["types"]:
|
||||||
|
kind = type["kind"]
|
||||||
|
|
||||||
|
check_type(kind, type, valid_data_types)
|
||||||
|
valid_data_types[type["name"]] = True
|
||||||
|
|
||||||
|
if "description" in type:
|
||||||
|
write_doc(file, type["description"])
|
||||||
|
|
||||||
|
if kind == "handle":
|
||||||
|
check_allowed_keys(type, ["name", "kind"], ["const", "parent", "description", "deprecated"])
|
||||||
|
if "parent" in type and type["parent"] not in handles:
|
||||||
|
raise UnknownTypeError(type["parent"], type["name"])
|
||||||
|
# @todo In the future, let's write these as `struct *` so the compiler can help us with type checking.
|
||||||
|
type["type"] = "void*" if not type.get("const", False) else "const void*"
|
||||||
|
write_simple_type(file, type)
|
||||||
|
handles.append(type["name"])
|
||||||
|
elif kind == "alias":
|
||||||
|
check_allowed_keys(type, ["name", "kind", "type"], ["description", "deprecated"])
|
||||||
|
write_simple_type(file, type)
|
||||||
|
elif kind == "enum":
|
||||||
|
check_allowed_keys(type, ["name", "kind", "values"], ["description", "deprecated"])
|
||||||
|
write_enum_type(file, type)
|
||||||
|
elif kind == "function":
|
||||||
|
check_allowed_keys(type, ["name", "kind", "return_value", "arguments"], ["description", "deprecated"])
|
||||||
|
write_function_type(file, type)
|
||||||
|
elif kind == "struct":
|
||||||
|
check_allowed_keys(type, ["name", "kind", "members"], ["description", "deprecated"])
|
||||||
|
write_struct_type(file, type)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unknown kind of type: {kind}")
|
||||||
|
|
||||||
|
for interface in data["interface"]:
|
||||||
|
check_type("function", interface, valid_data_types)
|
||||||
|
check_allowed_keys(
|
||||||
|
interface,
|
||||||
|
["name", "return_value", "arguments", "since", "description"],
|
||||||
|
["see", "legacy_type_name", "deprecated"],
|
||||||
|
)
|
||||||
|
write_interface(file, interface)
|
||||||
|
|
||||||
|
file.write("""\
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
# Serialize back into JSON in order to see if the formatting remains the same.
|
||||||
|
def check_formatting(buffer, data, filename):
|
||||||
|
buffer2 = json.dumps(data, indent=4)
|
||||||
|
|
||||||
|
lines1 = buffer.splitlines()
|
||||||
|
lines2 = buffer2.splitlines()
|
||||||
|
|
||||||
|
diff = difflib.unified_diff(
|
||||||
|
lines1,
|
||||||
|
lines2,
|
||||||
|
fromfile="a/" + filename,
|
||||||
|
tofile="b/" + filename,
|
||||||
|
lineterm="",
|
||||||
|
)
|
||||||
|
|
||||||
|
diff = list(diff)
|
||||||
|
if len(diff) > 0:
|
||||||
|
print(" *** Apply this patch to fix: ***\n")
|
||||||
|
print("\n".join(diff))
|
||||||
|
raise Exception(f"Formatting issues in {filename}")
|
||||||
|
|
||||||
|
|
||||||
|
def check_allowed_keys(data, required, optional=[]):
|
||||||
|
keys = data.keys()
|
||||||
|
allowed = required + optional
|
||||||
|
for k in keys:
|
||||||
|
if k not in allowed:
|
||||||
|
raise Exception(f"Found unknown key '{k}'")
|
||||||
|
for r in required:
|
||||||
|
if r not in keys:
|
||||||
|
raise Exception(f"Missing required key '{r}'")
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownTypeError(Exception):
|
||||||
|
def __init__(self, unknown, parent, item=None):
|
||||||
|
self.unknown = unknown
|
||||||
|
self.parent = parent
|
||||||
|
if item:
|
||||||
|
msg = f"Unknown type '{unknown}' for '{item}' used in '{parent}'"
|
||||||
|
else:
|
||||||
|
msg = f"Unknown type '{unknown}' used in '{parent}'"
|
||||||
|
super().__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def base_type_name(type_name):
|
||||||
|
if type_name.startswith("const "):
|
||||||
|
type_name = type_name[6:]
|
||||||
|
if type_name.endswith("*"):
|
||||||
|
type_name = type_name[:-1]
|
||||||
|
return type_name
|
||||||
|
|
||||||
|
|
||||||
|
def format_type_and_name(type, name=None):
|
||||||
|
ret = type
|
||||||
|
if ret[-1] == "*":
|
||||||
|
ret = ret[:-1] + " *"
|
||||||
|
if name:
|
||||||
|
if ret[-1] == "*":
|
||||||
|
ret = ret + name
|
||||||
|
else:
|
||||||
|
ret = ret + " " + name
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def check_type(kind, type, valid_data_types):
|
||||||
|
if kind == "alias":
|
||||||
|
if base_type_name(type["type"]) not in valid_data_types:
|
||||||
|
raise UnknownTypeError(type["type"], type["name"])
|
||||||
|
elif kind == "struct":
|
||||||
|
for member in type["members"]:
|
||||||
|
if base_type_name(member["type"]) not in valid_data_types:
|
||||||
|
raise UnknownTypeError(member["type"], type["name"], member["name"])
|
||||||
|
elif kind == "function":
|
||||||
|
for arg in type["arguments"]:
|
||||||
|
if base_type_name(arg["type"]) not in valid_data_types:
|
||||||
|
raise UnknownTypeError(arg["type"], type["name"], arg.get("name"))
|
||||||
|
if "return_value" in type:
|
||||||
|
if base_type_name(type["return_value"]["type"]) not in valid_data_types:
|
||||||
|
raise UnknownTypeError(type["return_value"]["type"], type["name"])
|
||||||
|
|
||||||
|
|
||||||
|
def write_doc(file, doc, indent=""):
|
||||||
|
if len(doc) == 1:
|
||||||
|
file.write(f"{indent}/* {doc[0]} */\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
first = True
|
||||||
|
for line in doc:
|
||||||
|
if first:
|
||||||
|
file.write(indent + "/*")
|
||||||
|
first = False
|
||||||
|
else:
|
||||||
|
file.write(indent + " *")
|
||||||
|
|
||||||
|
if line != "":
|
||||||
|
file.write(" " + line)
|
||||||
|
file.write("\n")
|
||||||
|
file.write(indent + " */\n")
|
||||||
|
|
||||||
|
|
||||||
|
def make_deprecated_note(type):
|
||||||
|
if "deprecated" not in type:
|
||||||
|
return ""
|
||||||
|
return f" /* {type['deprecated']} */"
|
||||||
|
|
||||||
|
|
||||||
|
def write_simple_type(file, type):
|
||||||
|
file.write(f"typedef {format_type_and_name(type['type'], type['name'])};{make_deprecated_note(type)}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def write_enum_type(file, enum):
|
||||||
|
file.write("typedef enum {\n")
|
||||||
|
for value in enum["values"]:
|
||||||
|
check_allowed_keys(value, ["name", "value"], ["description", "deprecated"])
|
||||||
|
if "description" in value:
|
||||||
|
write_doc(file, value["description"], "\t")
|
||||||
|
file.write(f"\t{value['name']} = {value['value']},\n")
|
||||||
|
file.write(f"}} {enum['name']};{make_deprecated_note(enum)}\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
def make_args_text(args):
|
||||||
|
combined = []
|
||||||
|
for arg in args:
|
||||||
|
check_allowed_keys(arg, ["type"], ["name", "description"])
|
||||||
|
combined.append(format_type_and_name(arg["type"], arg.get("name")))
|
||||||
|
return ", ".join(combined)
|
||||||
|
|
||||||
|
|
||||||
|
def write_function_type(file, fn):
|
||||||
|
args_text = make_args_text(fn["arguments"]) if ("arguments" in fn) else ""
|
||||||
|
name_and_args = f"(*{fn['name']})({args_text})"
|
||||||
|
file.write(
|
||||||
|
f"typedef {format_type_and_name(fn['return_value']['type'], name_and_args)};{make_deprecated_note(fn)}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def write_struct_type(file, struct):
|
||||||
|
file.write("typedef struct {\n")
|
||||||
|
for member in struct["members"]:
|
||||||
|
check_allowed_keys(member, ["name", "type"], ["description"])
|
||||||
|
if "description" in member:
|
||||||
|
write_doc(file, member["description"], "\t")
|
||||||
|
file.write(f"\t{format_type_and_name(member['type'], member['name'])};\n")
|
||||||
|
file.write(f"}} {struct['name']};{make_deprecated_note(struct)}\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
def write_interface(file, interface):
|
||||||
|
doc = [
|
||||||
|
f"@name {interface['name']}",
|
||||||
|
f"@since {interface['since']}",
|
||||||
|
]
|
||||||
|
|
||||||
|
if "deprecated" in interface:
|
||||||
|
if interface["deprecated"].lower().startswith("deprecated "):
|
||||||
|
parts = interface["deprecated"].split(" ", 1)
|
||||||
|
interface["deprecated"] = parts[1]
|
||||||
|
doc.append(f"@deprecated {interface['deprecated']}")
|
||||||
|
|
||||||
|
doc += [
|
||||||
|
"",
|
||||||
|
interface["description"][0],
|
||||||
|
]
|
||||||
|
|
||||||
|
if len(interface["description"]) > 1:
|
||||||
|
doc.append("")
|
||||||
|
doc += interface["description"][1:]
|
||||||
|
|
||||||
|
if "arguments" in interface:
|
||||||
|
doc.append("")
|
||||||
|
for arg in interface["arguments"]:
|
||||||
|
if "description" not in arg:
|
||||||
|
raise Exception(f"Interface function {interface['name']} is missing docs for {arg['name']} argument")
|
||||||
|
arg_doc = " ".join(arg["description"])
|
||||||
|
doc.append(f"@param {arg['name']} {arg_doc}")
|
||||||
|
|
||||||
|
if "return_value" in interface and interface["return_value"]["type"] != "void":
|
||||||
|
if "description" not in interface["return_value"]:
|
||||||
|
raise Exception(f"Interface function {interface['name']} is missing docs for return value")
|
||||||
|
ret_doc = " ".join(interface["return_value"]["description"])
|
||||||
|
doc.append("")
|
||||||
|
doc.append(f"@return {ret_doc}")
|
||||||
|
|
||||||
|
if "see" in interface:
|
||||||
|
doc.append("")
|
||||||
|
for see in interface["see"]:
|
||||||
|
doc.append(f"@see {see}")
|
||||||
|
|
||||||
|
file.write("/**\n")
|
||||||
|
for d in doc:
|
||||||
|
if d != "":
|
||||||
|
file.write(f" * {d}\n")
|
||||||
|
else:
|
||||||
|
file.write(" *\n")
|
||||||
|
file.write(" */\n")
|
||||||
|
|
||||||
|
fn = interface.copy()
|
||||||
|
if "deprecated" in fn:
|
||||||
|
del fn["deprecated"]
|
||||||
|
fn["name"] = "GDExtensionInterface" + "".join(word.capitalize() for word in interface["name"].split("_"))
|
||||||
|
write_function_type(file, fn)
|
||||||
|
|
||||||
|
file.write("\n")
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/extension/gdextension_interface.h"
|
#include "core/extension/gdextension_interface.gen.h"
|
||||||
#include "core/object/gdtype.h"
|
#include "core/object/gdtype.h"
|
||||||
#include "core/object/message_queue.h"
|
#include "core/object/message_queue.h"
|
||||||
#include "core/object/object_id.h"
|
#include "core/object/object_id.h"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "core/debugger/engine_debugger.h"
|
#include "core/debugger/engine_debugger.h"
|
||||||
#include "core/extension/extension_api_dump.h"
|
#include "core/extension/extension_api_dump.h"
|
||||||
#include "core/extension/gdextension_interface_dump.gen.h"
|
#include "core/extension/gdextension_interface_dump.gen.h"
|
||||||
|
#include "core/extension/gdextension_interface_header_generator.h"
|
||||||
#include "core/extension/gdextension_manager.h"
|
#include "core/extension/gdextension_manager.h"
|
||||||
#include "core/input/input.h"
|
#include "core/input/input.h"
|
||||||
#include "core/input/input_map.h"
|
#include "core/input/input_map.h"
|
||||||
@@ -278,6 +279,7 @@ static bool print_fps = false;
|
|||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
static bool editor_pseudolocalization = false;
|
static bool editor_pseudolocalization = false;
|
||||||
static bool dump_gdextension_interface = false;
|
static bool dump_gdextension_interface = false;
|
||||||
|
static bool dump_gdextension_interface_header = false;
|
||||||
static bool dump_extension_api = false;
|
static bool dump_extension_api = false;
|
||||||
static bool include_docs_in_extension_api_dump = false;
|
static bool include_docs_in_extension_api_dump = false;
|
||||||
static bool validate_extension_api = false;
|
static bool validate_extension_api = false;
|
||||||
@@ -697,6 +699,7 @@ void Main::print_help(const char *p_binary) {
|
|||||||
#endif
|
#endif
|
||||||
print_help_option("--build-solutions", "Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
print_help_option("--build-solutions", "Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
||||||
print_help_option("--dump-gdextension-interface", "Generate a GDExtension header file \"gdextension_interface.h\" in the current folder. This file is the base file required to implement a GDExtension.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
print_help_option("--dump-gdextension-interface", "Generate a GDExtension header file \"gdextension_interface.h\" in the current folder. This file is the base file required to implement a GDExtension.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
||||||
|
print_help_option("--dump-gdextension-interface-json", "Generate a JSON dump of the GDExtension interface named \"gdextension_interface.json\" in the current folder.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
||||||
print_help_option("--dump-extension-api", "Generate a JSON dump of the Godot API for GDExtension bindings named \"extension_api.json\" in the current folder.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
print_help_option("--dump-extension-api", "Generate a JSON dump of the Godot API for GDExtension bindings named \"extension_api.json\" in the current folder.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
||||||
print_help_option("--dump-extension-api-with-docs", "Generate JSON dump of the Godot API like the previous option, but including documentation.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
print_help_option("--dump-extension-api-with-docs", "Generate JSON dump of the Godot API like the previous option, but including documentation.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
||||||
print_help_option("--validate-extension-api <path>", "Validate an extension API file dumped (with one of the two previous options) from a previous version of the engine to ensure API compatibility.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
print_help_option("--validate-extension-api <path>", "Validate an extension API file dumped (with one of the two previous options) from a previous version of the engine to ensure API compatibility.\n", CLI_OPTION_AVAILABILITY_EDITOR);
|
||||||
@@ -1525,12 +1528,22 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
|
|||||||
// Register as an editor instance to use low-end fallback if relevant.
|
// Register as an editor instance to use low-end fallback if relevant.
|
||||||
editor = true;
|
editor = true;
|
||||||
cmdline_tool = true;
|
cmdline_tool = true;
|
||||||
dump_gdextension_interface = true;
|
dump_gdextension_interface_header = true;
|
||||||
print_line("Dumping GDExtension interface header file");
|
print_line("Dumping GDExtension interface header file");
|
||||||
// Hack. Not needed but otherwise we end up detecting that this should
|
// Hack. Not needed but otherwise we end up detecting that this should
|
||||||
// run the project instead of a cmdline tool.
|
// run the project instead of a cmdline tool.
|
||||||
// Needs full refactoring to fix properly.
|
// Needs full refactoring to fix properly.
|
||||||
main_args.push_back(arg);
|
main_args.push_back(arg);
|
||||||
|
} else if (arg == "--dump-gdextension-interface-json") {
|
||||||
|
// Register as an editor instance to use low-end fallback if relevant.
|
||||||
|
editor = true;
|
||||||
|
cmdline_tool = true;
|
||||||
|
dump_gdextension_interface = true;
|
||||||
|
print_line("Dumping GDExtension interface json file");
|
||||||
|
// Hack. Not needed but otherwise we end up detecting that this should
|
||||||
|
// run the project instead of a cmdline tool.
|
||||||
|
// Needs full refactoring to fix properly.
|
||||||
|
main_args.push_back(arg);
|
||||||
} else if (arg == "--dump-extension-api") {
|
} else if (arg == "--dump-extension-api") {
|
||||||
// Register as an editor instance to use low-end fallback if relevant.
|
// Register as an editor instance to use low-end fallback if relevant.
|
||||||
editor = true;
|
editor = true;
|
||||||
@@ -4041,7 +4054,11 @@ int Main::start() {
|
|||||||
// GDExtension API and interface.
|
// GDExtension API and interface.
|
||||||
{
|
{
|
||||||
if (dump_gdextension_interface) {
|
if (dump_gdextension_interface) {
|
||||||
GDExtensionInterfaceDump::generate_gdextension_interface_file("gdextension_interface.h");
|
GDExtensionInterfaceDump::generate_gdextension_interface_file("gdextension_interface.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dump_gdextension_interface_header) {
|
||||||
|
GDExtensionInterfaceHeaderGenerator::generate_gdextension_interface_header("gdextension_interface.h");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dump_extension_api) {
|
if (dump_extension_api) {
|
||||||
@@ -4049,7 +4066,7 @@ int Main::start() {
|
|||||||
GDExtensionAPIDump::generate_extension_json_file("extension_api.json", include_docs_in_extension_api_dump);
|
GDExtensionAPIDump::generate_extension_json_file("extension_api.json", include_docs_in_extension_api_dump);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dump_gdextension_interface || dump_extension_api) {
|
if (dump_gdextension_interface || dump_gdextension_interface_header || dump_extension_api) {
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/extension/gdextension_interface.h"
|
#include "core/extension/gdextension_interface.gen.h"
|
||||||
|
|
||||||
enum ModuleInitializationLevel {
|
enum ModuleInitializationLevel {
|
||||||
MODULE_INITIALIZATION_LEVEL_CORE = GDEXTENSION_INITIALIZATION_CORE,
|
MODULE_INITIALIZATION_LEVEL_CORE = GDEXTENSION_INITIALIZATION_CORE,
|
||||||
|
|||||||
Reference in New Issue
Block a user