Przeglądaj źródła

Merge pull request #1244 from dsnopek/4.1-cherrypicks-3

Cherry-picks for the godot-cpp 4.1 branch - 3rd batch
Rémi Verschelde 1 rok temu
rodzic
commit
4eed2d7be0

+ 4 - 4
.github/workflows/ci.yml

@@ -83,7 +83,7 @@ jobs:
 
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           submodules: recursive
 
@@ -168,7 +168,7 @@ jobs:
     runs-on: ubuntu-20.04
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           submodules: recursive
 
@@ -192,7 +192,7 @@ jobs:
     runs-on: ubuntu-20.04
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           submodules: recursive
 
@@ -216,7 +216,7 @@ jobs:
     runs-on: windows-2019
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           submodules: recursive
 

+ 1 - 1
.github/workflows/static_checks.yml

@@ -11,7 +11,7 @@ jobs:
     runs-on: ubuntu-20.04
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       # Azure repositories are not reliable, we need to prevent Azure giving us packages.
       - name: Make apt sources.list use the default Ubuntu repositories

+ 0 - 1
SConstruct

@@ -5,7 +5,6 @@ import platform
 import sys
 import subprocess
 from binding_generator import scons_generate_bindings, scons_emit_files
-from SCons.Errors import UserError
 
 
 EnsureSConsVersion(4, 0)

+ 67 - 9
binding_generator.py

@@ -97,9 +97,10 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
             files.append(str(source_filename.as_posix()))
 
     for engine_class in api["classes"]:
-        # TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings.
+        # Generate code for the ClassDB singleton under a different name.
         if engine_class["name"] == "ClassDB":
-            continue
+            engine_class["name"] = "ClassDBSingleton"
+            engine_class["alias_for"] = "ClassDB"
         header_filename = include_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".hpp")
         source_filename = source_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".cpp")
         if headers:
@@ -1038,21 +1039,23 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
 
     # First create map of classes and singletons.
     for class_api in api["classes"]:
-        # TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings.
+        # Generate code for the ClassDB singleton under a different name.
         if class_api["name"] == "ClassDB":
-            continue
+            class_api["name"] = "ClassDBSingleton"
+            class_api["alias_for"] = "ClassDB"
         engine_classes[class_api["name"]] = class_api["is_refcounted"]
     for native_struct in api["native_structures"]:
         engine_classes[native_struct["name"]] = False
         native_structures.append(native_struct["name"])
 
     for singleton in api["singletons"]:
+        # Generate code for the ClassDB singleton under a different name.
+        if singleton["name"] == "ClassDB":
+            singleton["name"] = "ClassDBSingleton"
+            singleton["alias_for"] = "ClassDB"
         singletons.append(singleton["name"])
 
     for class_api in api["classes"]:
-        # TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings.
-        if class_api["name"] == "ClassDB":
-            continue
         # Check used classes for header include.
         used_classes = set()
         fully_used_classes = set()
@@ -1142,6 +1145,12 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
         else:
             fully_used_classes.add("Wrapped")
 
+        # In order to ensure that PtrToArg specializations for native structs are
+        # always used, let's move any of them into 'fully_used_classes'.
+        for type_name in used_classes:
+            if is_struct_type(type_name) and not is_included_struct_type(type_name):
+                fully_used_classes.add(type_name)
+
         for type_name in fully_used_classes:
             if type_name in used_classes:
                 used_classes.remove(type_name)
@@ -1244,7 +1253,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
     if len(fully_used_classes) > 0:
         result.append("")
 
-    if class_name != "Object":
+    if class_name != "Object" and class_name != "ClassDBSingleton":
         result.append("#include <godot_cpp/core/class_db.hpp>")
         result.append("")
         result.append("#include <type_traits>")
@@ -1265,7 +1274,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
     inherits = class_api["inherits"] if "inherits" in class_api else "Wrapped"
     result.append(f"class {class_name} : public {inherits} {{")
 
-    result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})")
+    if "alias_for" in class_api:
+        result.append(f"\tGDEXTENSION_CLASS_ALIAS({class_name}, {class_api['alias_for']}, {inherits})")
+    else:
+        result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})")
     result.append("")
 
     result.append("public:")
@@ -1423,6 +1435,51 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
                 result.append(f'VARIANT_ENUM_CAST({class_name}::{enum_api["name"]});')
         result.append("")
 
+    if class_name == "ClassDBSingleton":
+        result.append("#define CLASSDB_SINGLETON_FORWARD_METHODS \\")
+        for method in class_api["methods"]:
+            # ClassDBSingleton shouldn't have any static or vararg methods, but if some appear later, lets skip them.
+            if vararg:
+                continue
+            if "is_static" in method and method["is_static"]:
+                continue
+
+            method_signature = "\tstatic "
+            if "return_type" in method:
+                method_signature += f'{correct_type(method["return_type"])} '
+            elif "return_value" in method:
+                method_signature += (
+                    correct_type(method["return_value"]["type"], method["return_value"].get("meta", None)) + " "
+                )
+            else:
+                method_signature += "void "
+
+            method_signature += f'{method["name"]}('
+
+            method_arguments = []
+            if "arguments" in method:
+                method_arguments = method["arguments"]
+
+            method_signature += make_function_parameters(
+                method_arguments, include_default=True, for_builtin=True, is_vararg=False
+            )
+
+            method_signature += ") { \\"
+
+            result.append(method_signature)
+
+            method_body = "\t\t"
+            if "return_type" in method or "return_value" in method:
+                method_body += "return "
+            method_body += f'ClassDBSingleton::get_singleton()->{method["name"]}('
+            method_body += ", ".join(map(lambda x: escape_identifier(x["name"]), method_arguments))
+            method_body += "); \\"
+
+            result.append(method_body)
+            result.append("\t} \\")
+        result.append("\t;")
+        result.append("")
+
     result.append(f"#endif // ! {header_guard}")
 
     return "\n".join(result)
@@ -2319,6 +2376,7 @@ def escape_identifier(id):
         "operator": "_operator",
         "typeof": "type_of",
         "typename": "type_name",
+        "enum": "_enum",
     }
     if id in cpp_keywords_map:
         return cpp_keywords_map[id]

+ 15 - 12
include/godot_cpp/classes/wrapped.hpp

@@ -132,16 +132,16 @@ protected:
 		return (void(::godot::Wrapped::*)(::godot::List<::godot::PropertyInfo> * p_list) const) & m_class::_get_property_list;                                                         \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
-	static bool (::godot::Wrapped::*_get_property_can_revert())(const ::godot::StringName &p_name) {                                                                                   \
-		return (bool(::godot::Wrapped::*)(const ::godot::StringName &p_name)) & m_class::_property_can_revert;                                                                         \
+	static bool (::godot::Wrapped::*_get_property_can_revert())(const ::godot::StringName &p_name) const {                                                                             \
+		return (bool(::godot::Wrapped::*)(const ::godot::StringName &p_name) const) & m_class::_property_can_revert;                                                                   \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
-	static bool (::godot::Wrapped::*_get_property_get_revert())(const ::godot::StringName &p_name, ::godot::Variant &) {                                                               \
-		return (bool(::godot::Wrapped::*)(const ::godot::StringName &p_name, ::godot::Variant &)) & m_class::_property_get_revert;                                                     \
+	static bool (::godot::Wrapped::*_get_property_get_revert())(const ::godot::StringName &p_name, ::godot::Variant &) const {                                                         \
+		return (bool(::godot::Wrapped::*)(const ::godot::StringName &p_name, ::godot::Variant &) const) & m_class::_property_get_revert;                                               \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
-	static ::godot::String (::godot::Wrapped::*_get_to_string())() {                                                                                                                   \
-		return (::godot::String(::godot::Wrapped::*)()) & m_class::_to_string;                                                                                                         \
+	static ::godot::String (::godot::Wrapped::*_get_to_string())() const {                                                                                                             \
+		return (::godot::String(::godot::Wrapped::*)() const) & m_class::_to_string;                                                                                                   \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
 	template <class T, class B>                                                                                                                                                        \
@@ -306,7 +306,7 @@ public:
 	};
 
 // Don't use this for your classes, use GDCLASS() instead.
-#define GDEXTENSION_CLASS(m_class, m_inherits)                                                                             \
+#define GDEXTENSION_CLASS_ALIAS(m_class, m_alias_for, m_inherits)                                                          \
 private:                                                                                                                   \
 	void operator=(const m_class &p_rval) {}                                                                               \
                                                                                                                            \
@@ -338,15 +338,15 @@ protected:
 		return nullptr;                                                                                                    \
 	}                                                                                                                      \
                                                                                                                            \
-	static bool (Wrapped::*_get_property_can_revert())(const ::godot::StringName &p_name) {                                \
+	static bool (Wrapped::*_get_property_can_revert())(const ::godot::StringName &p_name) const {                          \
 		return nullptr;                                                                                                    \
 	}                                                                                                                      \
                                                                                                                            \
-	static bool (Wrapped::*_get_property_get_revert())(const ::godot::StringName &p_name, Variant &) {                     \
+	static bool (Wrapped::*_get_property_get_revert())(const ::godot::StringName &p_name, Variant &) const {               \
 		return nullptr;                                                                                                    \
 	}                                                                                                                      \
                                                                                                                            \
-	static String (Wrapped::*_get_to_string())() {                                                                         \
+	static String (Wrapped::*_get_to_string())() const {                                                                   \
 		return nullptr;                                                                                                    \
 	}                                                                                                                      \
                                                                                                                            \
@@ -354,7 +354,7 @@ public:
 	static void initialize_class() {}                                                                                      \
                                                                                                                            \
 	static ::godot::StringName &get_class_static() {                                                                       \
-		static ::godot::StringName string_name = ::godot::StringName(#m_class);                                            \
+		static ::godot::StringName string_name = ::godot::StringName(#m_alias_for);                                        \
 		return string_name;                                                                                                \
 	}                                                                                                                      \
                                                                                                                            \
@@ -379,6 +379,9 @@ public:
 		_gde_binding_free_callback,                                                                                        \
 		_gde_binding_reference_callback,                                                                                   \
 	};                                                                                                                     \
-	m_class() : m_class(#m_class) {}
+	m_class() : m_class(#m_alias_for) {}
+
+// Don't use this for your classes, use GDCLASS() instead.
+#define GDEXTENSION_CLASS(m_class, m_inherits) GDEXTENSION_CLASS_ALIAS(m_class, m_class, m_inherits)
 
 #endif // GODOT_WRAPPED_HPP

+ 4 - 0
include/godot_cpp/core/class_db.hpp

@@ -38,6 +38,8 @@
 #include <godot_cpp/core/method_bind.hpp>
 #include <godot_cpp/core/object.hpp>
 
+#include <godot_cpp/classes/class_db_singleton.hpp>
+
 #include <list>
 #include <set>
 #include <string>
@@ -146,6 +148,8 @@ public:
 
 	static void initialize(GDExtensionInitializationLevel p_level);
 	static void deinitialize(GDExtensionInitializationLevel p_level);
+
+	CLASSDB_SINGLETON_FORWARD_METHODS;
 };
 
 #define BIND_CONSTANT(m_constant) \

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

@@ -168,6 +168,7 @@ MAKE_PTRARG_BY_REFERENCE(Variant);
 
 template <class T>
 struct PtrToArg<T *> {
+	static_assert(std::is_base_of<Object, T>::value, "Cannot encode non-Object value as an Object");
 	_FORCE_INLINE_ static T *convert(const void *p_ptr) {
 		return reinterpret_cast<T *>(godot::internal::get_object_instance_binding(*reinterpret_cast<GDExtensionObjectPtr *>(const_cast<void *>(p_ptr))));
 	}
@@ -179,6 +180,7 @@ struct PtrToArg<T *> {
 
 template <class T>
 struct PtrToArg<const T *> {
+	static_assert(std::is_base_of<Object, T>::value, "Cannot encode non-Object value as an Object");
 	_FORCE_INLINE_ static const T *convert(const void *p_ptr) {
 		return reinterpret_cast<const T *>(godot::internal::get_object_instance_binding(*reinterpret_cast<GDExtensionObjectPtr *>(const_cast<void *>(p_ptr))));
 	}

+ 42 - 13
src/godot.cpp

@@ -34,6 +34,7 @@
 #include <godot_cpp/classes/wrapped.hpp>
 #include <godot_cpp/core/class_db.hpp>
 #include <godot_cpp/core/memory.hpp>
+#include <godot_cpp/core/version.hpp>
 #include <godot_cpp/variant/variant.hpp>
 
 #include <godot_cpp/core/error_macros.hpp>
@@ -192,9 +193,15 @@ GDExtensionBinding::Callback GDExtensionBinding::init_callback = nullptr;
 GDExtensionBinding::Callback GDExtensionBinding::terminate_callback = nullptr;
 GDExtensionInitializationLevel GDExtensionBinding::minimum_initialization_level = GDEXTENSION_INITIALIZATION_CORE;
 
-#define LOAD_PROC_ADDRESS(m_name, m_type)                                           \
-	internal::gdextension_interface_##m_name = (m_type)p_get_proc_address(#m_name); \
-	ERR_FAIL_NULL_V_MSG(internal::gdextension_interface_##m_name, false, "Unable to load GDExtension interface function " #m_name "()")
+#define ERR_PRINT_EARLY(m_msg) \
+	internal::gdextension_interface_print_error(m_msg, FUNCTION_STR, __FILE__, __LINE__, false)
+
+#define LOAD_PROC_ADDRESS(m_name, m_type)                                               \
+	internal::gdextension_interface_##m_name = (m_type)p_get_proc_address(#m_name);     \
+	if (!internal::gdextension_interface_##m_name) {                                    \
+		ERR_PRINT_EARLY("Unable to load GDExtension interface function " #m_name "()"); \
+		return false;                                                                   \
+	}
 
 // Partial definition of the legacy interface so we can detect it and show an error.
 typedef struct {
@@ -217,14 +224,15 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
 	if (raw_interface[0] == 4 && raw_interface[1] == 0) {
 		// Use the legacy interface only to give a nice error.
 		LegacyGDExtensionInterface *legacy_interface = (LegacyGDExtensionInterface *)p_get_proc_address;
-		internal::gdextension_interface_print_error_with_message = (GDExtensionInterfacePrintErrorWithMessage)legacy_interface->print_error_with_message;
-		ERR_FAIL_V_MSG(false, "Cannot load a GDExtension built for Godot 4.1+ in Godot 4.0.");
+		internal::gdextension_interface_print_error = (GDExtensionInterfacePrintError)legacy_interface->print_error;
+		ERR_PRINT_EARLY("Cannot load a GDExtension built for Godot 4.1+ in Godot 4.0.");
+		return false;
 	}
 
-	// Load the "print_error_with_message" function first (needed by the ERR_FAIL_NULL_V_MSG() macro).
-	internal::gdextension_interface_print_error_with_message = (GDExtensionInterfacePrintErrorWithMessage)p_get_proc_address("print_error_with_message");
-	if (!internal::gdextension_interface_print_error_with_message) {
-		printf("ERROR: Unable to load GDExtension interface function print_error_with_message().\n");
+	// Load the "print_error" function first (needed by the ERR_PRINT_EARLY() macro).
+	internal::gdextension_interface_print_error = (GDExtensionInterfacePrintError)p_get_proc_address("print_error");
+	if (!internal::gdextension_interface_print_error) {
+		printf("ERROR: Unable to load GDExtension interface function print_error().\n");
 		return false;
 	}
 
@@ -233,10 +241,33 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
 	internal::token = p_library;
 
 	LOAD_PROC_ADDRESS(get_godot_version, GDExtensionInterfaceGetGodotVersion);
+	internal::gdextension_interface_get_godot_version(&internal::godot_version);
+
+	// Check that godot-cpp was compiled using an extension_api.json older or at the
+	// same version as the Godot that is loading it.
+	bool compatible;
+	if (internal::godot_version.major != GODOT_VERSION_MAJOR) {
+		compatible = internal::godot_version.major > GODOT_VERSION_MAJOR;
+	} else if (internal::godot_version.minor != GODOT_VERSION_MINOR) {
+		compatible = internal::godot_version.minor > GODOT_VERSION_MINOR;
+	} else {
+		compatible = internal::godot_version.patch >= GODOT_VERSION_PATCH;
+	}
+	if (!compatible) {
+		// We need to use snprintf() here because vformat() uses Variant, and we haven't loaded
+		// the GDExtension interface far enough to use Variants yet.
+		char msg[128];
+		snprintf(msg, 128, "Cannot load a GDExtension built for Godot %d.%d.%d using an older version of Godot (%d.%d.%d).",
+				GODOT_VERSION_MAJOR, GODOT_VERSION_MINOR, GODOT_VERSION_PATCH,
+				internal::godot_version.major, internal::godot_version.minor, internal::godot_version.patch);
+		ERR_PRINT_EARLY(msg);
+		return false;
+	}
+
 	LOAD_PROC_ADDRESS(mem_alloc, GDExtensionInterfaceMemAlloc);
 	LOAD_PROC_ADDRESS(mem_realloc, GDExtensionInterfaceMemRealloc);
 	LOAD_PROC_ADDRESS(mem_free, GDExtensionInterfaceMemFree);
-	LOAD_PROC_ADDRESS(print_error, GDExtensionInterfacePrintError);
+	LOAD_PROC_ADDRESS(print_error_with_message, GDExtensionInterfacePrintErrorWithMessage);
 	LOAD_PROC_ADDRESS(print_warning, GDExtensionInterfacePrintWarning);
 	LOAD_PROC_ADDRESS(print_warning_with_message, GDExtensionInterfacePrintWarningWithMessage);
 	LOAD_PROC_ADDRESS(print_script_error, GDExtensionInterfacePrintScriptError);
@@ -368,9 +399,6 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
 	LOAD_PROC_ADDRESS(editor_add_plugin, GDExtensionInterfaceEditorAddPlugin);
 	LOAD_PROC_ADDRESS(editor_remove_plugin, GDExtensionInterfaceEditorRemovePlugin);
 
-	// Load the Godot version.
-	internal::gdextension_interface_get_godot_version(&internal::godot_version);
-
 	r_initialization->initialize = initialize_level;
 	r_initialization->deinitialize = deinitialize_level;
 	r_initialization->minimum_initialization_level = minimum_initialization_level;
@@ -384,6 +412,7 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
 }
 
 #undef LOAD_PROC_ADDRESS
+#undef ERR_PRINT_EARLY
 
 void GDExtensionBinding::initialize_level(void *userdata, GDExtensionInitializationLevel p_level) {
 	ClassDB::current_level = p_level;

+ 1 - 1
test/project/project.godot

@@ -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.1")
 config/icon="res://icon.png"
 
 [native_extensions]

+ 1 - 1
tools/android.py

@@ -29,7 +29,7 @@ def generate(env):
 
     if env["arch"] not in ("arm64", "x86_64", "arm32", "x86_32"):
         print("Only arm64, x86_64, arm32, and x86_32 are supported on Android. Exiting.")
-        Exit()
+        env.Exit(1)
 
     if sys.platform == "win32" or sys.platform == "msys":
         my_spawn.configure(env)

+ 4 - 3
tools/godotcpp.py

@@ -3,6 +3,7 @@ import os, sys, platform
 from SCons.Variables import EnumVariable, PathVariable, BoolVariable
 from SCons.Tool import Tool
 from SCons.Builder import Builder
+from SCons.Errors import UserError
 
 from binding_generator import scons_generate_bindings, scons_emit_files
 
@@ -226,7 +227,7 @@ def generate(env):
                 env["arch"] = "x86_32"
             else:
                 print("Unsupported CPU architecture: " + host_machine)
-                Exit()
+                env.Exit(1)
 
     print("Building for architecture " + env["arch"] + " on platform " + env["platform"])
 
@@ -284,8 +285,8 @@ def _godot_cpp(env):
     )
     # Forces bindings regeneration.
     if env["generate_bindings"]:
-        AlwaysBuild(bindings)
-        NoCache(bindings)
+        env.AlwaysBuild(bindings)
+        env.NoCache(bindings)
 
     # Sources to compile
     sources = []

+ 1 - 1
tools/javascript.py

@@ -8,7 +8,7 @@ def exists(env):
 def generate(env):
     if env["arch"] not in ("wasm32"):
         print("Only wasm32 supported on web. Exiting.")
-        Exit()
+        env.Exit(1)
 
     if "EM_CONFIG" in os.environ:
         env["ENV"] = os.environ

+ 1 - 1
tools/macos.py

@@ -20,7 +20,7 @@ def exists(env):
 def generate(env):
     if env["arch"] not in ("universal", "arm64", "x86_64"):
         print("Only universal, arm64, and x86_64 are supported on macOS. Exiting.")
-        Exit()
+        env.Exit(1)
 
     if sys.platform == "darwin":
         # Use clang on macOS by default