Add GDNative JSON generator for the builtin API

Which can be used by language bindings to generate code statically. This
is generated as a different file from the class API because it has
different requirements (the builtin types have constructors and don't
have signals), so bindings can better make use of each JSON file without
extra parsing.

This also cleans up a bit the old API generator, mainly initializing
structs and renaming "instanciable" to the more correct "instantiable".

The argument description in help text was updated to better reflect how
it should be used. The <path> argument is mandatory.
This commit is contained in:
George Marques 2021-01-12 14:12:40 -03:00
parent a4aaead346
commit 8a8dbd76b1
No known key found for this signature in database
GPG key ID: 046BD46A3201E43D
4 changed files with 462 additions and 75 deletions

View file

@ -265,27 +265,27 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print("\n");
OS::get_singleton()->print("General options:\n");
OS::get_singleton()->print(" -h, --help Display this help message.\n");
OS::get_singleton()->print(" --version Display the version string.\n");
OS::get_singleton()->print(" -v, --verbose Use verbose stdout mode.\n");
OS::get_singleton()->print(" --quiet Quiet mode, silences stdout messages. Errors are still displayed.\n");
OS::get_singleton()->print(" -h, --help Display this help message.\n");
OS::get_singleton()->print(" --version Display the version string.\n");
OS::get_singleton()->print(" -v, --verbose Use verbose stdout mode.\n");
OS::get_singleton()->print(" --quiet Quiet mode, silences stdout messages. Errors are still displayed.\n");
OS::get_singleton()->print("\n");
OS::get_singleton()->print("Run options:\n");
#ifdef TOOLS_ENABLED
OS::get_singleton()->print(" -e, --editor Start the editor instead of running the scene.\n");
OS::get_singleton()->print(" -p, --project-manager Start the project manager, even if a project is auto-detected.\n");
OS::get_singleton()->print(" -e, --editor Start the editor instead of running the scene.\n");
OS::get_singleton()->print(" -p, --project-manager Start the project manager, even if a project is auto-detected.\n");
#endif
OS::get_singleton()->print(" -q, --quit Quit after the first iteration.\n");
OS::get_singleton()->print(" -l, --language <locale> Use a specific locale (<locale> being a two-letter code).\n");
OS::get_singleton()->print(" --path <directory> Path to a project (<directory> must contain a 'project.godot' file).\n");
OS::get_singleton()->print(" -u, --upwards Scan folders upwards for project.godot file.\n");
OS::get_singleton()->print(" --main-pack <file> Path to a pack (.pck) file to load.\n");
OS::get_singleton()->print(" --render-thread <mode> Render thread mode ('unsafe', 'safe', 'separate').\n");
OS::get_singleton()->print(" --remote-fs <address> Remote filesystem (<host/IP>[:<port>] address).\n");
OS::get_singleton()->print(" --remote-fs-password <password> Password for remote filesystem.\n");
OS::get_singleton()->print(" -q, --quit Quit after the first iteration.\n");
OS::get_singleton()->print(" -l, --language <locale> Use a specific locale (<locale> being a two-letter code).\n");
OS::get_singleton()->print(" --path <directory> Path to a project (<directory> must contain a 'project.godot' file).\n");
OS::get_singleton()->print(" -u, --upwards Scan folders upwards for project.godot file.\n");
OS::get_singleton()->print(" --main-pack <file> Path to a pack (.pck) file to load.\n");
OS::get_singleton()->print(" --render-thread <mode> Render thread mode ('unsafe', 'safe', 'separate').\n");
OS::get_singleton()->print(" --remote-fs <address> Remote filesystem (<host/IP>[:<port>] address).\n");
OS::get_singleton()->print(" --remote-fs-password <password> Password for remote filesystem.\n");
OS::get_singleton()->print(" --audio-driver <driver> Audio driver [");
OS::get_singleton()->print(" --audio-driver <driver> Audio driver [");
for (int i = 0; i < AudioDriverManager::get_driver_count(); i++) {
if (i > 0) {
OS::get_singleton()->print(", ");
@ -294,7 +294,7 @@ void Main::print_help(const char *p_binary) {
}
OS::get_singleton()->print("].\n");
OS::get_singleton()->print(" --display-driver <driver> Display driver (and rendering driver) [");
OS::get_singleton()->print(" --display-driver <driver> Display driver (and rendering driver) [");
for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
if (i > 0) {
OS::get_singleton()->print(", ");
@ -311,26 +311,26 @@ void Main::print_help(const char *p_binary) {
}
OS::get_singleton()->print("].\n");
OS::get_singleton()->print(" --rendering-driver <driver> Rendering driver (depends on display driver).\n");
OS::get_singleton()->print(" --rendering-driver <driver> Rendering driver (depends on display driver).\n");
OS::get_singleton()->print(" --text-driver <driver> Text driver (Fonts, BiDi, shaping)\n");
OS::get_singleton()->print(" --text-driver <driver> Text driver (Fonts, BiDi, shaping)\n");
OS::get_singleton()->print("\n");
#ifndef SERVER_ENABLED
OS::get_singleton()->print("Display options:\n");
OS::get_singleton()->print(" -f, --fullscreen Request fullscreen mode.\n");
OS::get_singleton()->print(" -m, --maximized Request a maximized window.\n");
OS::get_singleton()->print(" -w, --windowed Request windowed mode.\n");
OS::get_singleton()->print(" -t, --always-on-top Request an always-on-top window.\n");
OS::get_singleton()->print(" --resolution <W>x<H> Request window resolution.\n");
OS::get_singleton()->print(" --position <X>,<Y> Request window position.\n");
OS::get_singleton()->print(" --low-dpi Force low-DPI mode (macOS and Windows only).\n");
OS::get_singleton()->print(" --no-window Disable window creation (Windows only). Useful together with --script.\n");
OS::get_singleton()->print(" --enable-vsync-via-compositor When vsync is enabled, vsync via the OS' window compositor (Windows only).\n");
OS::get_singleton()->print(" --disable-vsync-via-compositor Disable vsync via the OS' window compositor (Windows only).\n");
OS::get_singleton()->print(" --single-window Use a single window (no separate subwindows).\n");
OS::get_singleton()->print(" --tablet-driver Tablet input driver (");
OS::get_singleton()->print(" -f, --fullscreen Request fullscreen mode.\n");
OS::get_singleton()->print(" -m, --maximized Request a maximized window.\n");
OS::get_singleton()->print(" -w, --windowed Request windowed mode.\n");
OS::get_singleton()->print(" -t, --always-on-top Request an always-on-top window.\n");
OS::get_singleton()->print(" --resolution <W>x<H> Request window resolution.\n");
OS::get_singleton()->print(" --position <X>,<Y> Request window position.\n");
OS::get_singleton()->print(" --low-dpi Force low-DPI mode (macOS and Windows only).\n");
OS::get_singleton()->print(" --no-window Disable window creation (Windows only). Useful together with --script.\n");
OS::get_singleton()->print(" --enable-vsync-via-compositor When vsync is enabled, vsync via the OS' window compositor (Windows only).\n");
OS::get_singleton()->print(" --disable-vsync-via-compositor Disable vsync via the OS' window compositor (Windows only).\n");
OS::get_singleton()->print(" --single-window Use a single window (no separate subwindows).\n");
OS::get_singleton()->print(" --tablet-driver Tablet input driver (");
for (int i = 0; i < OS::get_singleton()->get_tablet_driver_count(); i++) {
if (i != 0)
OS::get_singleton()->print(", ");
@ -341,43 +341,44 @@ void Main::print_help(const char *p_binary) {
#endif
OS::get_singleton()->print("Debug options:\n");
OS::get_singleton()->print(" -d, --debug Debug (local stdout debugger).\n");
OS::get_singleton()->print(" -b, --breakpoints Breakpoint list as source::line comma-separated pairs, no spaces (use %%20 instead).\n");
OS::get_singleton()->print(" --profiling Enable profiling in the script debugger.\n");
OS::get_singleton()->print(" --vk-layers Enable Vulkan Validation layers for debugging.\n");
OS::get_singleton()->print(" -d, --debug Debug (local stdout debugger).\n");
OS::get_singleton()->print(" -b, --breakpoints Breakpoint list as source::line comma-separated pairs, no spaces (use %%20 instead).\n");
OS::get_singleton()->print(" --profiling Enable profiling in the script debugger.\n");
OS::get_singleton()->print(" --vk-layers Enable Vulkan Validation layers for debugging.\n");
#if DEBUG_ENABLED
OS::get_singleton()->print(" --gpu-abort Abort on GPU errors (usually validation layer errors), may help see the problem if your system freezes.\n");
OS::get_singleton()->print(" --gpu-abort Abort on GPU errors (usually validation layer errors), may help see the problem if your system freezes.\n");
#endif
OS::get_singleton()->print(" --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n");
OS::get_singleton()->print(" --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n");
#if defined(DEBUG_ENABLED) && !defined(SERVER_ENABLED)
OS::get_singleton()->print(" --debug-collisions Show collision shapes when running the scene.\n");
OS::get_singleton()->print(" --debug-navigation Show navigation polygons when running the scene.\n");
OS::get_singleton()->print(" --debug-collisions Show collision shapes when running the scene.\n");
OS::get_singleton()->print(" --debug-navigation Show navigation polygons when running the scene.\n");
#endif
OS::get_singleton()->print(" --frame-delay <ms> Simulate high CPU load (delay each frame by <ms> milliseconds).\n");
OS::get_singleton()->print(" --time-scale <scale> Force time scale (higher values are faster, 1.0 is normal speed).\n");
OS::get_singleton()->print(" --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n");
OS::get_singleton()->print(" --disable-crash-handler Disable crash handler when supported by the platform code.\n");
OS::get_singleton()->print(" --fixed-fps <fps> Force a fixed number of frames per second. This setting disables real-time synchronization.\n");
OS::get_singleton()->print(" --print-fps Print the frames per second to the stdout.\n");
OS::get_singleton()->print(" --profile-gpu Show a simple profile of the tasks that took more time during frame rendering.\n");
OS::get_singleton()->print(" --frame-delay <ms> Simulate high CPU load (delay each frame by <ms> milliseconds).\n");
OS::get_singleton()->print(" --time-scale <scale> Force time scale (higher values are faster, 1.0 is normal speed).\n");
OS::get_singleton()->print(" --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n");
OS::get_singleton()->print(" --disable-crash-handler Disable crash handler when supported by the platform code.\n");
OS::get_singleton()->print(" --fixed-fps <fps> Force a fixed number of frames per second. This setting disables real-time synchronization.\n");
OS::get_singleton()->print(" --print-fps Print the frames per second to the stdout.\n");
OS::get_singleton()->print(" --profile-gpu Show a simple profile of the tasks that took more time during frame rendering.\n");
OS::get_singleton()->print("\n");
OS::get_singleton()->print("Standalone tools:\n");
OS::get_singleton()->print(" -s, --script <script> Run a script.\n");
OS::get_singleton()->print(" --check-only Only parse for errors and quit (use with --script).\n");
OS::get_singleton()->print(" -s, --script <script> Run a script.\n");
OS::get_singleton()->print(" --check-only Only parse for errors and quit (use with --script).\n");
#ifdef TOOLS_ENABLED
OS::get_singleton()->print(" --export <preset> <path> Export the project using the given preset and matching release template. The preset name should match one defined in export_presets.cfg.\n");
OS::get_singleton()->print(" <path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. 'builds/game.exe'). The target directory should exist.\n");
OS::get_singleton()->print(" --export-debug <preset> <path> Same as --export, but using the debug template.\n");
OS::get_singleton()->print(" --export-pack <preset> <path> Same as --export, but only export the game pack for the given preset. The <path> extension determines whether it will be in PCK or ZIP format.\n");
OS::get_singleton()->print(" --doctool <path> Dump the engine API reference to the given <path> in XML format, merging if existing files are found.\n");
OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n");
OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n");
OS::get_singleton()->print(" --export <preset> <path> Export the project using the given preset and matching release template. The preset name should match one defined in export_presets.cfg.\n");
OS::get_singleton()->print(" <path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. 'builds/game.exe'). The target directory should exist.\n");
OS::get_singleton()->print(" --export-debug <preset> <path> Same as --export, but using the debug template.\n");
OS::get_singleton()->print(" --export-pack <preset> <path> Same as --export, but only export the game pack for the given preset. The <path> extension determines whether it will be in PCK or ZIP format.\n");
OS::get_singleton()->print(" --doctool <path> Dump the engine API reference to the given <path> in XML format, merging if existing files are found.\n");
OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n");
OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n");
#ifdef DEBUG_METHODS_ENABLED
OS::get_singleton()->print(" --gdnative-generate-json-api Generate JSON dump of the Godot API for GDNative bindings.\n");
OS::get_singleton()->print(" --gdnative-generate-json-api <path> Generate JSON dump of the Godot API for GDNative bindings and save it on the file specified in <path>.\n");
OS::get_singleton()->print(" --gdnative-generate-json-builtin-api <path> Generate JSON dump of the Godot API of the builtin Variant types and utility functions for GDNative bindings and save it on the file specified in <path>.\n");
#endif
#ifdef TESTS_ENABLED
OS::get_singleton()->print(" --test [--help] Run unit tests. Use --test --help for more information.\n");
OS::get_singleton()->print(" --test [--help] Run unit tests. Use --test --help for more information.\n");
#endif
OS::get_singleton()->print("\n");
#endif
@ -879,7 +880,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
auto_build_solutions = true;
editor = true;
#ifdef DEBUG_METHODS_ENABLED
} else if (I->get() == "--gdnative-generate-json-api") {
} else if (I->get() == "--gdnative-generate-json-api" || I->get() == "--gdnative-generate-json-builtin-api") {
// Register as an editor instance to use low-end fallback if relevant.
editor = true;

View file

@ -36,6 +36,7 @@
#include "core/core_constants.h"
#include "core/object/class_db.h"
#include "core/os/file_access.h"
#include "core/string/string_builder.h"
#include "core/templates/pair.h"
// helper stuff
@ -65,14 +66,14 @@ struct MethodAPI {
Map<int, Variant> default_arguments;
int argument_count;
bool has_varargs;
bool is_editor;
bool is_noscript;
bool is_const;
bool is_reverse;
bool is_virtual;
bool is_from_script;
int argument_count = 0;
bool has_varargs = false;
bool is_editor = false;
bool is_noscript = false;
bool is_const = false;
bool is_reverse = false;
bool is_virtual = false;
bool is_from_script = false;
};
struct PropertyAPI {
@ -80,12 +81,14 @@ struct PropertyAPI {
String getter;
String setter;
String type;
int index;
int index = 0;
};
struct ConstantAPI {
String constant_name;
int constant_value;
int constant_value = 0;
Variant builtin_constant_value; // For builtin types;
String builtin_constant_type; // For builtin types;
};
struct SignalAPI {
@ -100,23 +103,34 @@ struct EnumAPI {
List<Pair<int, String>> values;
};
struct OperatorAPI { // For builtin types;
String name;
int oper = Variant::OP_MAX;
String other_type;
String return_type;
};
struct ClassAPI {
String class_name;
String super_class_name;
ClassDB::APIType api_type;
ClassDB::APIType api_type = ClassDB::API_NONE;
bool is_singleton;
bool is_singleton = false;
String singleton_name;
bool is_instanciable;
bool is_instantiable = false;
// @Unclear
bool is_reference;
bool is_reference = false;
bool has_indexing = false; // For builtin types.
bool is_keyed = false; // For builtin types.
List<MethodAPI> methods;
List<MethodAPI> constructors; // For builtin types.
List<PropertyAPI> properties;
List<ConstantAPI> constants;
List<SignalAPI> signals_;
List<EnumAPI> enums;
List<OperatorAPI> operators; // For builtin types.
};
static String get_type_name(const PropertyInfo &info) {
@ -180,7 +194,7 @@ List<ClassAPI> generate_c_api_classes() {
global_constants_api.api_type = ClassDB::API_CORE;
global_constants_api.is_singleton = true;
global_constants_api.singleton_name = "CoreConstants";
global_constants_api.is_instanciable = false;
global_constants_api.is_instantiable = false;
const int constants_count = CoreConstants::get_global_constant_count();
for (int i = 0; i < constants_count; ++i) {
ConstantAPI constant_api;
@ -195,6 +209,10 @@ List<ClassAPI> generate_c_api_classes() {
for (List<StringName>::Element *e = classes.front(); e != nullptr; e = e->next()) {
StringName class_name = e->get();
if (!ClassDB::is_class_exposed(class_name)) {
continue;
}
ClassAPI class_api;
class_api.api_type = ClassDB::get_api_type(e->get());
class_api.class_name = class_name;
@ -209,7 +227,7 @@ List<ClassAPI> generate_c_api_classes() {
class_api.singleton_name = name;
}
}
class_api.is_instanciable = !class_api.is_singleton && ClassDB::can_instance(class_name);
class_api.is_instantiable = !class_api.is_singleton && ClassDB::can_instance(class_name);
{
List<StringName> inheriters;
@ -401,6 +419,191 @@ List<ClassAPI> generate_c_api_classes() {
return api;
}
/*
* Reads the builtin Variant API to a list
*/
List<ClassAPI> generate_c_builtin_api_types() {
List<ClassAPI> api;
// Special class for the utility methods.
{
ClassAPI utility_api;
utility_api.class_name = "Utilities";
utility_api.is_instantiable = false;
List<StringName> utility_functions;
Variant::get_utility_function_list(&utility_functions);
for (const List<StringName>::Element *E = utility_functions.front(); E; E = E->next()) {
const StringName &function_name = E->get();
MethodAPI function_api;
function_api.method_name = function_name;
function_api.has_varargs = Variant::is_utility_function_vararg(function_name);
function_api.argument_count = function_api.has_varargs ? 0 : Variant::get_utility_function_argument_count(function_name);
function_api.is_const = Variant::get_utility_function_type(function_name) == Variant::UTILITY_FUNC_TYPE_MATH;
for (int i = 0; i < function_api.argument_count; i++) {
function_api.argument_names.push_back(Variant::get_utility_function_argument_name(function_name, i));
Variant::Type arg_type = Variant::get_utility_function_argument_type(function_name, i);
function_api.argument_types.push_back(arg_type == Variant::NIL ? "Variant" : Variant::get_type_name(arg_type));
}
if (Variant::has_utility_function_return_value(function_name)) {
Variant::Type ret_type = Variant::get_utility_function_return_type(function_name);
function_api.return_type = ret_type == Variant::NIL ? "Variant" : Variant::get_type_name(ret_type);
} else {
function_api.return_type = "void";
}
utility_api.methods.push_back(function_api);
}
api.push_back(utility_api);
}
for (int t = 0; t < Variant::VARIANT_MAX; t++) {
Variant::Type type = (Variant::Type)t;
ClassAPI class_api;
class_api.class_name = Variant::get_type_name(type);
class_api.is_instantiable = true;
class_api.has_indexing = Variant::has_indexing(type);
class_api.is_keyed = Variant::is_keyed(type);
// Types that are passed by reference.
switch (type) {
case Variant::OBJECT:
case Variant::DICTIONARY:
case Variant::ARRAY:
case Variant::PACKED_BYTE_ARRAY:
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY:
case Variant::PACKED_FLOAT32_ARRAY:
case Variant::PACKED_FLOAT64_ARRAY:
case Variant::PACKED_STRING_ARRAY:
case Variant::PACKED_VECTOR2_ARRAY:
case Variant::PACKED_VECTOR3_ARRAY:
case Variant::PACKED_COLOR_ARRAY:
class_api.is_reference = true;
break;
default:
class_api.is_reference = false;
break;
}
// Methods.
List<StringName> methods;
Variant::get_builtin_method_list(type, &methods);
for (const List<StringName>::Element *E = methods.front(); E; E = E->next()) {
const StringName &method_name = E->get();
MethodAPI method_api;
method_api.method_name = method_name;
method_api.argument_count = Variant::get_builtin_method_argument_count(type, method_name);
method_api.has_varargs = Variant::is_builtin_method_vararg(type, method_name);
method_api.is_const = Variant::is_builtin_method_const(type, method_name);
for (int i = 0; i < method_api.argument_count; i++) {
method_api.argument_names.push_back(Variant::get_builtin_method_argument_name(type, method_name, i));
Variant::Type arg_type = Variant::get_builtin_method_argument_type(type, method_name, i);
method_api.argument_types.push_back(arg_type == Variant::NIL ? "Variant" : Variant::get_type_name(arg_type));
}
Vector<Variant> default_arguments = Variant::get_builtin_method_default_arguments(type, method_name);
int default_start = method_api.argument_names.size() - default_arguments.size();
for (int i = 0; i < default_arguments.size(); i++) {
method_api.default_arguments[default_start + i] = default_arguments[i];
}
if (Variant::has_builtin_method_return_value(type, method_name)) {
Variant::Type ret_type = Variant::get_builtin_method_return_type(type, method_name);
method_api.return_type = ret_type == Variant::NIL ? "Variant" : Variant::get_type_name(ret_type);
} else {
method_api.return_type = "void";
}
class_api.methods.push_back(method_api);
}
// Constructors.
for (int c = 0; c < Variant::get_constructor_count(type); c++) {
MethodAPI constructor_api;
constructor_api.method_name = Variant::get_type_name(type);
constructor_api.argument_count = Variant::get_constructor_argument_count(type, c);
constructor_api.return_type = Variant::get_type_name(type);
for (int i = 0; i < constructor_api.argument_count; i++) {
constructor_api.argument_names.push_back(Variant::get_constructor_argument_name(type, c, i));
Variant::Type arg_type = Variant::get_constructor_argument_type(type, c, i);
constructor_api.argument_types.push_back(arg_type == Variant::NIL ? "Variant" : Variant::get_type_name(arg_type));
}
class_api.constructors.push_back(constructor_api);
}
// Constants.
List<StringName> constants;
Variant::get_constants_for_type(type, &constants);
for (const List<StringName>::Element *E = constants.front(); E; E = E->next()) {
const StringName &constant_name = E->get();
ConstantAPI constant_api;
constant_api.constant_name = constant_name;
constant_api.builtin_constant_value = Variant::get_constant_value(type, constant_name);
constant_api.builtin_constant_type = Variant::get_type_name(constant_api.builtin_constant_value.get_type());
class_api.constants.push_back(constant_api);
}
// Members.
List<StringName> members;
Variant::get_member_list(type, &members);
for (const List<StringName>::Element *E = members.front(); E; E = E->next()) {
const StringName &member_name = E->get();
PropertyAPI member_api;
member_api.name = member_name;
Variant::Type member_type = Variant::get_member_type(type, member_name);
member_api.type = member_type == Variant::NIL ? "Variant" : Variant::get_type_name(member_type);
class_api.properties.push_back(member_api);
}
// Operators.
for (int op = 0; op < Variant::OP_MAX; op++) {
Variant::Operator oper = (Variant::Operator)op;
for (int ot = 0; ot < Variant::VARIANT_MAX; ot++) {
Variant::Type other_type = (Variant::Type)ot;
if (!Variant::get_validated_operator_evaluator(oper, type, other_type)) {
continue;
}
OperatorAPI oper_api;
oper_api.name = Variant::get_operator_name(oper);
oper_api.oper = oper;
oper_api.other_type = Variant::get_type_name(other_type);
oper_api.return_type = Variant::get_type_name(Variant::get_operator_return_type(oper, type, other_type));
class_api.operators.push_back(oper_api);
}
}
api.push_back(class_api);
}
return api;
}
/*
* Generates the JSON source from the API in p_api
*/
@ -421,9 +624,8 @@ static List<String> generate_c_api_json(const List<ClassAPI> &p_api) {
source.push_back(String("\t\t\"api_type\": \"") + (api.api_type == ClassDB::API_CORE ? "core" : (api.api_type == ClassDB::API_EDITOR ? "tools" : "none")) + "\",\n");
source.push_back(String("\t\t\"singleton\": ") + (api.is_singleton ? "true" : "false") + ",\n");
source.push_back("\t\t\"singleton_name\": \"" + api.singleton_name + "\",\n");
source.push_back(String("\t\t\"instanciable\": ") + (api.is_instanciable ? "true" : "false") + ",\n");
source.push_back(String("\t\t\"instantiable\": ") + (api.is_instantiable ? "true" : "false") + ",\n");
source.push_back(String("\t\t\"is_reference\": ") + (api.is_reference ? "true" : "false") + ",\n");
// @Unclear
source.push_back("\t\t\"constants\": {\n");
for (List<ConstantAPI>::Element *e = api.constants.front(); e; e = e->next()) {
@ -508,6 +710,164 @@ static List<String> generate_c_api_json(const List<ClassAPI> &p_api) {
return source;
}
static int indent_level = 0;
static void append_indented(StringBuilder &p_source, const String &p_text) {
for (int i = 0; i < indent_level; i++) {
p_source.append("\t");
}
p_source.append(p_text);
p_source.append("\n");
}
static void append_indented(StringBuilder &p_source, const char *p_text) {
for (int i = 0; i < indent_level; i++) {
p_source.append("\t");
}
p_source.append(p_text);
p_source.append("\n");
}
static void write_builtin_method(StringBuilder &p_source, const MethodAPI &p_method) {
append_indented(p_source, vformat(R"("name": "%s",)", p_method.method_name));
append_indented(p_source, vformat(R"("return_type": "%s",)", p_method.return_type));
append_indented(p_source, vformat(R"("is_const": %s,)", p_method.is_const ? "true" : "false"));
append_indented(p_source, vformat(R"("has_varargs": %s,)", p_method.has_varargs ? "true" : "false"));
append_indented(p_source, R"("arguments": [)");
indent_level++;
for (int i = 0; i < p_method.argument_count; i++) {
append_indented(p_source, "{");
indent_level++;
append_indented(p_source, vformat(R"("name": "%s",)", p_method.argument_names[i]));
append_indented(p_source, vformat(R"("type": "%s",)", p_method.argument_types[i]));
append_indented(p_source, vformat(R"("has_default_value": %s,)", p_method.default_arguments.has(i) ? "true" : "false"));
append_indented(p_source, vformat(R"("default_value": "%s")", p_method.default_arguments.has(i) ? p_method.default_arguments[i].operator String() : ""));
indent_level--;
append_indented(p_source, i < p_method.argument_count - 1 ? "}," : "}");
}
indent_level--;
append_indented(p_source, "]");
}
static List<String> generate_c_builtin_api_json(const List<ClassAPI> &p_api) {
StringBuilder source;
source.append("[\n");
indent_level = 1;
for (const List<ClassAPI>::Element *C = p_api.front(); C; C = C->next()) {
const ClassAPI &class_api = C->get();
append_indented(source, "{");
indent_level++;
append_indented(source, vformat(R"("name": "%s",)", class_api.class_name));
append_indented(source, vformat(R"("is_instantiable": %s,)", class_api.is_instantiable ? "true" : "false"));
append_indented(source, vformat(R"("is_reference": %s,)", class_api.is_reference ? "true" : "false"));
append_indented(source, vformat(R"("has_indexing": %s,)", class_api.has_indexing ? "true" : "false"));
append_indented(source, vformat(R"("is_keyed": %s,)", class_api.is_keyed ? "true" : "false"));
// Constructors.
append_indented(source, R"("constructors": [)");
indent_level++;
for (const List<MethodAPI>::Element *E = class_api.constructors.front(); E; E = E->next()) {
const MethodAPI &constructor = E->get();
append_indented(source, "{");
indent_level++;
write_builtin_method(source, constructor);
indent_level--;
append_indented(source, E->next() ? "}," : "}");
}
indent_level--;
append_indented(source, "],");
// Constants.
append_indented(source, R"("constants": [)");
indent_level++;
for (const List<ConstantAPI>::Element *E = class_api.constants.front(); E; E = E->next()) {
const ConstantAPI &constant = E->get();
append_indented(source, "{");
indent_level++;
append_indented(source, vformat(R"("name": "%s",)", constant.constant_name));
append_indented(source, vformat(R"("type": "%s",)", constant.builtin_constant_type));
append_indented(source, vformat(R"("value": "%s")", constant.builtin_constant_value.operator String()));
indent_level--;
append_indented(source, E->next() ? "}," : "}");
}
indent_level--;
append_indented(source, "],");
// Methods.
append_indented(source, R"("methods": [)");
indent_level++;
for (const List<MethodAPI>::Element *E = class_api.methods.front(); E; E = E->next()) {
const MethodAPI &method = E->get();
append_indented(source, "{");
indent_level++;
write_builtin_method(source, method);
indent_level--;
append_indented(source, E->next() ? "}," : "}");
}
indent_level--;
append_indented(source, "],");
// Members.
append_indented(source, R"("members": [)");
indent_level++;
for (const List<PropertyAPI>::Element *E = class_api.properties.front(); E; E = E->next()) {
const PropertyAPI &member = E->get();
append_indented(source, "{");
indent_level++;
append_indented(source, vformat(R"("name": "%s",)", member.name));
append_indented(source, vformat(R"("type": "%s")", member.type));
indent_level--;
append_indented(source, E->next() ? "}," : "}");
}
indent_level--;
append_indented(source, "],");
// Operators.
append_indented(source, R"("operators": [)");
indent_level++;
for (const List<OperatorAPI>::Element *E = class_api.operators.front(); E; E = E->next()) {
const OperatorAPI &oper = E->get();
append_indented(source, "{");
indent_level++;
append_indented(source, vformat(R"("name": "%s",)", oper.name));
append_indented(source, vformat(R"("operator": %d,)", oper.oper));
append_indented(source, vformat(R"("other_type": "%s",)", oper.other_type));
append_indented(source, vformat(R"("return_type": "%s")", oper.return_type));
indent_level--;
append_indented(source, E->next() ? "}," : "}");
}
indent_level--;
append_indented(source, "]");
indent_level--;
append_indented(source, C->next() ? "}," : "}");
}
indent_level--;
source.append("]\n");
List<String> result;
result.push_back(source.as_string());
return result;
}
#endif
/*
@ -526,3 +886,19 @@ Error generate_c_api(const String &p_path) {
return save_file(p_path, json_source);
#endif
}
/*
* Saves the builtin Godot API to a JSON file located at
* p_path
*/
Error generate_c_builtin_api(const String &p_path) {
#ifndef TOOLS_ENABLED
return ERR_BUG;
#else
List<ClassAPI> api = generate_c_builtin_api_types();
List<String> json_source = generate_c_builtin_api_json(api);
return save_file(p_path, json_source);
#endif
}

View file

@ -35,5 +35,6 @@
#include "core/typedefs.h"
Error generate_c_api(const String &p_path);
Error generate_c_builtin_api(const String &p_path);
#endif // API_GENERATOR_H

View file

@ -1257,6 +1257,15 @@ void NativeScriptLanguage::init() {
}
exit(0);
}
E = args.find("--gdnative-generate-json-builtin-api");
if (E && E->next()) {
if (generate_c_builtin_api(E->next()->get()) != OK) {
ERR_PRINT("Failed to generate C builtin API\n");
}
exit(0);
}
#endif
#ifdef TOOLS_ENABLED