ソースを参照

Merge pull request #1570 from dsnopek/4.2-cherrypicks-7

Cherry-picks for the godot-cpp 4.2 branch - 7th batch
David Snopek 1 年間 前
コミット
67e84c04f2

+ 4 - 5
.github/actions/godot-cache/action.yml → .github/actions/godot-cache-restore/action.yml

@@ -1,5 +1,5 @@
-name: Setup Godot build cache
-description: Setup Godot build cache.
+name: Restore Godot build cache
+description: Restore Godot build cache.
 inputs:
   cache-name:
     description: The cache base name (job name by default).
@@ -10,9 +10,8 @@ inputs:
 runs:
   using: "composite"
   steps:
-    # Upload cache on completion and check it out now
-    - name: Load .scons_cache directory
-      uses: actions/cache@v3
+    - name: Restore .scons_cache directory
+      uses: actions/cache/restore@v3
       with:
         path: ${{inputs.scons-cache}}
         key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}

+ 17 - 0
.github/actions/godot-cache-save/action.yml

@@ -0,0 +1,17 @@
+name: Save Godot build cache
+description: Save Godot build cache.
+inputs:
+  cache-name:
+    description: The cache base name (job name by default).
+    default: "${{github.job}}"
+  scons-cache:
+    description: The SCons cache path.
+    default: "${{github.workspace}}/.scons-cache/"
+runs:
+  using: "composite"
+  steps:
+    - name: Save SCons cache directory
+      uses: actions/cache/save@v4
+      with:
+        path: ${{inputs.scons-cache}}
+        key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}

+ 8 - 2
.github/workflows/ci.yml

@@ -99,8 +99,8 @@ jobs:
         with:
           submodules: recursive
 
-      - name: Setup Godot build cache
-        uses: ./.github/actions/godot-cache
+      - name: Restore Godot build cache
+        uses: ./.github/actions/godot-cache-restore
         with:
           cache-name: ${{ matrix.cache-name }}
         continue-on-error: true
@@ -153,6 +153,12 @@ jobs:
           cd test
           scons platform=${{ matrix.platform }} verbose=yes target=template_release ${{ matrix.flags }}
 
+      - name: Save Godot build cache
+        uses: ./.github/actions/godot-cache-save
+        with:
+          cache-name: ${{ matrix.cache-name }}
+        continue-on-error: true
+
       - name: Download latest Godot artifacts
         uses: dsnopek/action-download-artifact@1322f74e2dac9feed2ee76a32d9ae1ca3b4cf4e9
         if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION == 'master' }}

+ 12 - 0
CMakeLists.txt

@@ -5,6 +5,7 @@
 # GODOT_GDEXTENSION_DIR:		Path to the directory containing GDExtension interface header and API JSON file
 # GODOT_CPP_SYSTEM_HEADERS		Mark the header files as SYSTEM. This may be useful to suppress warnings in projects including this one.
 # GODOT_CPP_WARNING_AS_ERROR	Treat any warnings as errors
+# GODOT_ENABLE_HOT_RELOAD       Build with hot reload support. Defaults to YES for Debug-builds and NO for Release-builds.
 # GODOT_CUSTOM_API_FILE:		Path to a custom GDExtension API JSON file (takes precedence over `gdextension_dir`)
 # FLOAT_PRECISION:				Floating-point precision level ("single", "double")
 #
@@ -57,6 +58,13 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "")
 	set(CMAKE_BUILD_TYPE Debug)
 endif()
 
+# Hot reload is enabled by default in Debug-builds
+if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
+    option(GODOT_ENABLE_HOT_RELOAD "Build with hot reload support" ON)
+else()
+    option(GODOT_ENABLE_HOT_RELOAD "Build with hot reload support" OFF)
+endif()
+
 if(NOT DEFINED BITS)
 	set(BITS 32)
 	if(CMAKE_SIZEOF_VOID_P EQUAL 8)
@@ -116,6 +124,10 @@ else()
 	endif()
 endif()
 
+if (GODOT_ENABLE_HOT_RELOAD)
+    set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -D HOT_RELOAD_ENABLED")
+endif()
+
 # Generate source from the bindings file
 find_package(Python3 3.4 REQUIRED) # pathlib should be present
 if(GENERATE_TEMPLATE_GET_NODE)

+ 150 - 70
binding_generator.py

@@ -413,8 +413,14 @@ def generate_builtin_bindings(api, output_dir, build_config):
 
         builtin_header.append("")
 
+        includes = []
         for builtin in builtin_classes:
-            builtin_header.append(f"#include <godot_cpp/variant/{camel_to_snake(builtin)}.hpp>")
+            includes.append(f"godot_cpp/variant/{camel_to_snake(builtin)}.hpp")
+
+        includes.sort()
+
+        for include in includes:
+            builtin_header.append(f"#include <{include}>")
 
         builtin_header.append("")
 
@@ -470,11 +476,10 @@ def generate_builtin_class_vararg_method_implements_header(builtin_classes):
                 continue
 
             result += make_varargs_template(
-                method, "is_static" in method and method["is_static"], class_name, False, False, True
+                method, "is_static" in method and method["is_static"], class_name, False, True
             )
             result.append("")
 
-    result.append("")
     result.append(f"#endif // ! {header_guard}")
 
     return "\n".join(result)
@@ -499,36 +504,50 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
 
     # Special cases.
     if class_name == "String":
+        result.append("#include <godot_cpp/classes/global_constants.hpp>")
         result.append("#include <godot_cpp/variant/char_string.hpp>")
         result.append("#include <godot_cpp/variant/char_utils.hpp>")
-        result.append("#include <godot_cpp/classes/global_constants.hpp>")
+        result.append("")
 
     if class_name == "PackedStringArray":
         result.append("#include <godot_cpp/variant/string.hpp>")
+        result.append("")
     if class_name == "PackedColorArray":
         result.append("#include <godot_cpp/variant/color.hpp>")
+        result.append("")
     if class_name == "PackedVector2Array":
         result.append("#include <godot_cpp/variant/vector2.hpp>")
+        result.append("")
     if class_name == "PackedVector3Array":
         result.append("#include <godot_cpp/variant/vector3.hpp>")
+        result.append("")
 
     if is_packed_array(class_name):
         result.append("#include <godot_cpp/core/error_macros.hpp>")
         result.append("#include <initializer_list>")
+        result.append("")
 
     if class_name == "Array":
         result.append("#include <godot_cpp/variant/array_helpers.hpp>")
+        result.append("")
 
     if class_name == "Callable":
         result.append("#include <godot_cpp/variant/callable_custom.hpp>")
-
-    for include in fully_used_classes:
-        if include == "TypedArray":
-            result.append("#include <godot_cpp/variant/typed_array.hpp>")
-        else:
-            result.append(f"#include <godot_cpp/{get_include_path(include)}>")
+        result.append("")
 
     if len(fully_used_classes) > 0:
+        includes = []
+        for include in fully_used_classes:
+            if include == "TypedArray":
+                includes.append("godot_cpp/variant/typed_array.hpp")
+            else:
+                includes.append(f"godot_cpp/{get_include_path(include)}")
+
+        includes.sort()
+
+        for include in includes:
+            result.append(f"#include <{include}>")
+
         result.append("")
 
     result.append("#include <gdextension_interface.h>")
@@ -606,7 +625,7 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
     result.append("public:")
 
     result.append(
-        f"\t_FORCE_INLINE_ GDExtensionTypePtr _native_ptr() const {{ return const_cast<uint8_t (*)[{snake_class_name}_SIZE]>(&opaque); }}"
+        f"\t_FORCE_INLINE_ GDExtensionTypePtr _native_ptr() const {{ return const_cast<uint8_t(*)[{snake_class_name}_SIZE]>(&opaque); }}"
     )
 
     copy_constructor_index = -1
@@ -719,14 +738,14 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
 
     if "operators" in builtin_api:
         for operator in builtin_api["operators"]:
-            if operator["name"] not in ["in", "xor"]:
+            if is_valid_cpp_operator(operator["name"]):
                 if "right_type" in operator:
                     result.append(
-                        f'\t{correct_type(operator["return_type"])} operator{operator["name"]}({type_for_parameter(operator["right_type"])}p_other) const;'
+                        f'\t{correct_type(operator["return_type"])} operator{get_operator_cpp_name(operator["name"])}({type_for_parameter(operator["right_type"])}p_other) const;'
                     )
                 else:
                     result.append(
-                        f'\t{correct_type(operator["return_type"])} operator{operator["name"].replace("unary", "")}() const;'
+                        f'\t{correct_type(operator["return_type"])} operator{get_operator_cpp_name(operator["name"])}() const;'
                     )
 
     # Copy assignment.
@@ -785,7 +804,7 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
         result.append(f"\tconst {return_type} *ptr() const;")
         result.append(f"\t{return_type} *ptrw();")
         iterators = """
-    struct Iterator {
+	struct Iterator {
 		_FORCE_INLINE_ $TYPE &operator*() const {
 			return *elem_ptr;
 		}
@@ -847,19 +866,17 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
 	}
 	_FORCE_INLINE_ ConstIterator end() const {
 		return ConstIterator(ptr() + size());
-	}
-"""
+	}"""
         result.append(iterators.replace("$TYPE", return_type))
         init_list = """
-    _FORCE_INLINE_ $CLASS(std::initializer_list<$TYPE> p_init) {
+	_FORCE_INLINE_ $CLASS(std::initializer_list<$TYPE> p_init) {
 		ERR_FAIL_COND(resize(p_init.size()) != 0);
 
 		size_t i = 0;
 		for (const $TYPE &element : p_init) {
 			set(i++, element);
 		}
-	}
-"""
+	}"""
         result.append(init_list.replace("$TYPE", return_type).replace("$CLASS", class_name))
 
     if class_name == "Array":
@@ -898,7 +915,9 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
     result.append("")
     result.append("} // namespace godot")
 
+    result.append("")
     result.append(f"#endif // ! {header_guard}")
+    result.append("")
 
     return "\n".join(result)
 
@@ -912,7 +931,6 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
 
     add_header(f"{snake_class_name}.cpp", result)
 
-    result.append("")
     result.append(f"#include <godot_cpp/variant/{snake_class_name}.hpp>")
     result.append("")
     result.append("#include <godot_cpp/core/binder_common.hpp>")
@@ -921,10 +939,16 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
     result.append("")
 
     # Only used since the "fully used" is included in header already.
-    for include in used_classes:
-        result.append(f"#include <godot_cpp/{get_include_path(include)}>")
-
     if len(used_classes) > 0:
+        includes = []
+        for included in used_classes:
+            includes.append(f"godot_cpp/{get_include_path(included)}")
+
+        includes.sort()
+
+        for included in includes:
+            result.append(f"#include <{included}>")
+
         result.append("")
 
     result.append("#include <godot_cpp/core/builtin_ptrcall.hpp>")
@@ -1158,10 +1182,10 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
 
     if "operators" in builtin_api:
         for operator in builtin_api["operators"]:
-            if operator["name"] not in ["in", "xor"]:
+            if is_valid_cpp_operator(operator["name"]):
                 if "right_type" in operator:
                     result.append(
-                        f'{correct_type(operator["return_type"])} {class_name}::operator{operator["name"]}({type_for_parameter(operator["right_type"])}p_other) const {{'
+                        f'{correct_type(operator["return_type"])} {class_name}::operator{get_operator_cpp_name(operator["name"])}({type_for_parameter(operator["right_type"])}p_other) const {{'
                     )
                     (encode, arg_name) = get_encoded_arg("other", operator["right_type"], None)
                     result += encode
@@ -1171,10 +1195,10 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
                     result.append("}")
                 else:
                     result.append(
-                        f'{correct_type(operator["return_type"])} {class_name}::operator{operator["name"].replace("unary", "")}() const {{'
+                        f'{correct_type(operator["return_type"])} {class_name}::operator{get_operator_cpp_name(operator["name"])}() const {{'
                     )
                     result.append(
-                        f'\treturn internal::_call_builtin_operator_ptr<{get_gdextension_type(correct_type(operator["return_type"]))}>(_method_bindings.operator_{get_operator_id_name(operator["name"])}, (GDExtensionConstTypePtr)&opaque, (GDExtensionConstTypePtr)nullptr);'
+                        f'\treturn internal::_call_builtin_operator_ptr<{get_gdextension_type(correct_type(operator["return_type"]))}>(_method_bindings.operator_{get_operator_id_name(operator["name"])}, (GDExtensionConstTypePtr)&opaque, (GDExtensionConstTypePtr) nullptr);'
                     )
                     result.append("}")
                 result.append("")
@@ -1210,6 +1234,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
 
     result.append("")
     result.append("} //namespace godot")
+    result.append("")
 
     return "\n".join(result)
 
@@ -1385,11 +1410,18 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
 
         result.append("")
 
-        for included in used_classes:
-            result.append(f"#include <godot_cpp/{get_include_path(included)}>")
+        if len(used_classes) > 0:
+            includes = []
+            for included in used_classes:
+                includes.append(f"godot_cpp/{get_include_path(included)}")
+
+            includes.sort()
 
-        if len(used_classes) == 0:
+            for include in includes:
+                result.append(f"#include <{include}>")
+        else:
             result.append("#include <godot_cpp/core/method_ptrcall.hpp>")
+
         result.append("")
 
         result.append("namespace godot {")
@@ -1429,16 +1461,23 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
 
     result.append("")
 
-    for included in fully_used_classes:
-        if included == "TypedArray":
-            result.append("#include <godot_cpp/variant/typed_array.hpp>")
-        else:
-            result.append(f"#include <godot_cpp/{get_include_path(included)}>")
+    if len(fully_used_classes) > 0:
+        includes = []
+        for included in fully_used_classes:
+            if included == "TypedArray":
+                includes.append("godot_cpp/variant/typed_array.hpp")
+            else:
+                includes.append(f"godot_cpp/{get_include_path(included)}")
+
+        includes.sort()
+
+        for include in includes:
+            result.append(f"#include <{include}>")
+
+        result.append("")
 
     if class_name == "EditorPlugin":
         result.append("#include <godot_cpp/classes/editor_plugin_registration.hpp>")
-
-    if len(fully_used_classes) > 0:
         result.append("")
 
     if class_name != "Object" and class_name != "ClassDBSingleton":
@@ -1477,7 +1516,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
         result.append("")
 
     result.append("public:")
-    result.append("")
 
     if "enums" in class_api:
         for enum_api in class_api["enums"]:
@@ -1510,6 +1548,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
 
             vararg = "is_vararg" in method and method["is_vararg"]
 
+            if vararg:
+                result.append("")
+                result.append("private:")
+
             method_signature = "\t"
             method_signature += make_signature(
                 class_name, method, for_header=True, use_template_get_node=use_template_get_node
@@ -1517,6 +1559,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
             result.append(method_signature + ";")
 
             if vararg:
+                result.append("")
+                result.append("public:")
                 # Add templated version.
                 result += make_varargs_template(method)
 
@@ -1531,6 +1575,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
             )
             result.append(method_signature + ";")
 
+        result.append("")
+
     result.append("protected:")
     # T is the custom class we want to register (from which the call initiates, going up the inheritance chain),
     # B is its base class (can be a custom class too, that's why we pass it).
@@ -1547,7 +1593,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
                     # If the method is different from the base class, it means T overrides it, so it needs to be bound.
                     # Note that with an `if constexpr`, the code inside the `if` will not even be compiled if the
                     # condition returns false (in such cases it can't compile due to ambiguity).
-                    f"\t\tif constexpr (!std::is_same_v<decltype(&B::{method_name}),decltype(&T::{method_name})>) {{"
+                    f"\t\tif constexpr (!std::is_same_v<decltype(&B::{method_name}), decltype(&T::{method_name})>) {{"
                 )
                 result.append(f"\t\t\tBIND_VIRTUAL_METHOD(T, {method_name});")
                 result.append("\t\t}")
@@ -1571,7 +1617,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
 
     if class_name == "WorkerThreadPool":
         result.append("\tenum {")
-        result.append("\tINVALID_TASK_ID = -1")
+        result.append("\t\tINVALID_TASK_ID = -1")
         result.append("\t};")
         result.append("\ttypedef int64_t TaskID;")
         result.append("\ttypedef int64_t GroupID;")
@@ -1583,8 +1629,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
         )
 
     if class_name == "Object":
-        result.append("")
-
         result.append("\ttemplate <typename T>")
         result.append("\tstatic T *cast_to(Object *p_object);")
 
@@ -1599,7 +1643,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
             "\tT *get_node(const NodePath &p_path) const { return Object::cast_to<T>(get_node_internal(p_path)); }"
         )
 
-    result.append("")
     result.append("};")
     result.append("")
 
@@ -1683,7 +1726,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
 
             result.append(method_body)
             result.append("\t} \\")
-        result.append("\t;")
+        result.append("\t")
         result.append("")
 
         result.append("#define CLASSDB_SINGLETON_VARIANT_CAST \\")
@@ -1695,10 +1738,11 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
                 else:
                     result.append(f'\tVARIANT_ENUM_CAST({class_api["alias_for"]}::{enum_api["name"]}); \\')
 
-        result.append("\t;")
+        result.append("\t")
         result.append("")
 
     result.append(f"#endif // ! {header_guard}")
+    result.append("")
 
     return "\n".join(result)
 
@@ -1720,10 +1764,16 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
     result.append("#include <godot_cpp/core/error_macros.hpp>")
     result.append("")
 
-    for included in used_classes:
-        result.append(f"#include <godot_cpp/{get_include_path(included)}>")
-
     if len(used_classes) > 0:
+        includes = []
+        for included in used_classes:
+            includes.append(f"godot_cpp/{get_include_path(included)}")
+
+        includes.sort()
+
+        for included in includes:
+            result.append(f"#include <{included}>")
+
         result.append("")
 
     result.append("namespace godot {")
@@ -1873,8 +1923,8 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
                 result.append(method_signature)
             result.append("")
 
+    result.append("} // namespace godot")
     result.append("")
-    result.append("} // namespace godot ")
 
     return "\n".join(result)
 
@@ -1902,23 +1952,24 @@ def generate_global_constants(api, output_dir):
     header.append("namespace godot {")
     header.append("")
 
-    for constant in api["global_constants"]:
-        header.append(f'\tconst int64_t {escape_identifier(constant["name"])} = {constant["value"]};')
+    if len(api["global_constants"]) > 0:
+        for constant in api["global_constants"]:
+            header.append(f'const int64_t {escape_identifier(constant["name"])} = {constant["value"]};')
 
-    header.append("")
+        header.append("")
 
     for enum_def in api["global_enums"]:
         if enum_def["name"].startswith("Variant."):
             continue
 
         if enum_def["is_bitfield"]:
-            header.append(f'\tenum {enum_def["name"]} : uint64_t {{')
+            header.append(f'enum {enum_def["name"]} : uint64_t {{')
         else:
-            header.append(f'\tenum {enum_def["name"]} {{')
+            header.append(f'enum {enum_def["name"]} {{')
 
         for value in enum_def["values"]:
-            header.append(f'\t\t{value["name"]} = {value["value"]},')
-        header.append("\t};")
+            header.append(f'\t{value["name"]} = {value["value"]},')
+        header.append("};")
         header.append("")
 
     header.append("} // namespace godot")
@@ -2031,11 +2082,17 @@ def generate_utility_functions(api, output_dir):
     for function in api["utility_functions"]:
         vararg = "is_vararg" in function and function["is_vararg"]
 
+        if vararg:
+            header.append("")
+            header.append("private:")
+
         function_signature = "\t"
         function_signature += make_signature("UtilityFunctions", function, for_header=True, static=True)
         header.append(function_signature + ";")
 
         if vararg:
+            header.append("")
+            header.append("public:")
             # Add templated version.
             header += make_varargs_template(function, static=True)
 
@@ -2056,8 +2113,8 @@ def generate_utility_functions(api, output_dir):
 
     source.append("#include <godot_cpp/variant/utility_functions.hpp>")
     source.append("")
-    source.append("#include <godot_cpp/core/error_macros.hpp>")
     source.append("#include <godot_cpp/core/engine_ptrcall.hpp>")
+    source.append("#include <godot_cpp/core/error_macros.hpp>")
     source.append("")
     source.append("namespace godot {")
     source.append("")
@@ -2156,7 +2213,7 @@ def make_function_parameters(parameters, include_default=False, for_builtin=Fals
         signature.append(parameter)
 
     if is_vararg:
-        signature.append("const Args&... p_args")
+        signature.append("const Args &...p_args")
 
     return ", ".join(signature)
 
@@ -2214,9 +2271,6 @@ def make_signature(
         if "is_virtual" in function_data and function_data["is_virtual"]:
             function_signature += "virtual "
 
-        if is_vararg:
-            function_signature += "private: "
-
         if static:
             function_signature += "static "
 
@@ -2269,7 +2323,6 @@ def make_varargs_template(
     function_data,
     static=False,
     class_befor_signature="",
-    with_public_declare=True,
     with_indent=True,
     for_builtin_classes=False,
 ):
@@ -2277,10 +2330,7 @@ def make_varargs_template(
 
     function_signature = ""
 
-    if with_public_declare:
-        function_signature = "public: "
-
-    function_signature += "template <typename... Args> "
+    result.append("template <typename... Args>")
 
     if static:
         function_signature += "static "
@@ -2323,7 +2373,7 @@ def make_varargs_template(
     function_signature += " {"
     result.append(function_signature)
 
-    args_array = f"\tstd::array<Variant, {len(method_arguments)} + sizeof...(Args)> variant_args {{ "
+    args_array = f"\tstd::array<Variant, {len(method_arguments)} + sizeof...(Args)> variant_args{{ "
     for argument in method_arguments:
         if argument["type"] == "Variant":
             args_array += escape_argument(argument["name"])
@@ -2572,8 +2622,6 @@ def correct_type(type_name, meta=None, use_alias=True):
     if meta is not None:
         if "int" in meta:
             return f"{meta}_t"
-        elif meta in type_conversion:
-            return type_conversion[type_name]
         else:
             return meta
     if type_name in type_conversion:
@@ -2685,6 +2733,38 @@ def get_operator_id_name(op):
     return op_id_map[op]
 
 
+def get_operator_cpp_name(op):
+    op_cpp_map = {
+        "==": "==",
+        "!=": "!=",
+        "<": "<",
+        "<=": "<=",
+        ">": ">",
+        ">=": ">=",
+        "+": "+",
+        "-": "-",
+        "*": "*",
+        "/": "/",
+        "unary-": "-",
+        "unary+": "+",
+        "%": "%",
+        "<<": "<<",
+        ">>": ">>",
+        "&": "&",
+        "|": "|",
+        "^": "^",
+        "~": "~",
+        "and": "&&",
+        "or": "||",
+        "not": "!",
+    }
+    return op_cpp_map[op]
+
+
+def is_valid_cpp_operator(op):
+    return op not in ["**", "xor", "in"]
+
+
 def get_default_value_for_type(type_name):
     if type_name == "int":
         return "0"

+ 13 - 9
include/godot_cpp/classes/wrapped.hpp

@@ -138,7 +138,7 @@ struct EngineClassRegistration {
 // every line of the macro different
 #define GDCLASS(m_class, m_inherits) /***********************************************************************************************************************************************/ \
 private:                                                                                                                                                                               \
-	void operator=(const m_class &p_rval) {}                                                                                                                                           \
+	void operator=(const m_class & /*p_rval*/) {}                                                                                                                                      \
 	friend class ::godot::ClassDB;                                                                                                                                                     \
                                                                                                                                                                                        \
 protected:                                                                                                                                                                             \
@@ -234,23 +234,27 @@ public:
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
 	static GDExtensionBool set_bind(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionConstVariantPtr p_value) {                                \
-		if (p_instance && m_class::_get_set()) {                                                                                                                                       \
+		if (p_instance) {                                                                                                                                                              \
+			if (m_inherits::set_bind(p_instance, p_name, p_value)) {                                                                                                                   \
+				return true;                                                                                                                                                           \
+			}                                                                                                                                                                          \
 			if (m_class::_get_set() != m_inherits::_get_set()) {                                                                                                                       \
 				m_class *cls = reinterpret_cast<m_class *>(p_instance);                                                                                                                \
 				return cls->_set(*reinterpret_cast<const ::godot::StringName *>(p_name), *reinterpret_cast<const ::godot::Variant *>(p_value));                                        \
 			}                                                                                                                                                                          \
-			return m_inherits::set_bind(p_instance, p_name, p_value);                                                                                                                  \
 		}                                                                                                                                                                              \
 		return false;                                                                                                                                                                  \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
 	static GDExtensionBool get_bind(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) {                                       \
-		if (p_instance && m_class::_get_get()) {                                                                                                                                       \
+		if (p_instance) {                                                                                                                                                              \
+			if (m_inherits::get_bind(p_instance, p_name, r_ret)) {                                                                                                                     \
+				return true;                                                                                                                                                           \
+			}                                                                                                                                                                          \
 			if (m_class::_get_get() != m_inherits::_get_get()) {                                                                                                                       \
 				m_class *cls = reinterpret_cast<m_class *>(p_instance);                                                                                                                \
 				return cls->_get(*reinterpret_cast<const ::godot::StringName *>(p_name), *reinterpret_cast<::godot::Variant *>(r_ret));                                                \
 			}                                                                                                                                                                          \
-			return m_inherits::get_bind(p_instance, p_name, r_ret);                                                                                                                    \
 		}                                                                                                                                                                              \
 		return false;                                                                                                                                                                  \
 	}                                                                                                                                                                                  \
@@ -329,7 +333,7 @@ public:
 		}                                                                                                                                                                              \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
-	static void free(void *data, GDExtensionClassInstancePtr ptr) {                                                                                                                    \
+	static void free(void * /*data*/, GDExtensionClassInstancePtr ptr) {                                                                                                               \
 		if (ptr) {                                                                                                                                                                     \
 			m_class *cls = reinterpret_cast<m_class *>(ptr);                                                                                                                           \
 			cls->~m_class();                                                                                                                                                           \
@@ -337,14 +341,14 @@ public:
 		}                                                                                                                                                                              \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
-	static void *_gde_binding_create_callback(void *p_token, void *p_instance) {                                                                                                       \
+	static void *_gde_binding_create_callback(void * /*p_token*/, void * /*p_instance*/) {                                                                                             \
 		return nullptr;                                                                                                                                                                \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
-	static void _gde_binding_free_callback(void *p_token, void *p_instance, void *p_binding) {                                                                                         \
+	static void _gde_binding_free_callback(void * /*p_token*/, void * /*p_instance*/, void * /*p_binding*/) {                                                                          \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
-	static GDExtensionBool _gde_binding_reference_callback(void *p_token, void *p_instance, GDExtensionBool p_reference) {                                                             \
+	static GDExtensionBool _gde_binding_reference_callback(void * /*p_token*/, void * /*p_instance*/, GDExtensionBool /*p_reference*/) {                                               \
 		return true;                                                                                                                                                                   \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \

+ 1 - 1
include/godot_cpp/templates/safe_refcount.hpp

@@ -132,7 +132,7 @@ public:
 		}
 	}
 
-	_ALWAYS_INLINE_ explicit SafeNumeric<T>(T p_value = static_cast<T>(0)) {
+	_ALWAYS_INLINE_ explicit SafeNumeric(T p_value = static_cast<T>(0)) {
 		set(p_value);
 	}
 };

+ 4 - 2
test/project/example.gdextension

@@ -29,8 +29,10 @@ android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so"
 android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so"
 ios.debug = "res://bin/libgdexample.ios.template_debug.xcframework"
 ios.release = "res://bin/libgdexample.ios.template_release.xcframework"
-web.debug.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.wasm"
-web.release.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.wasm"
+web.debug.threads.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.wasm"
+web.release.threads.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.wasm"
+web.debug.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.nothreads.wasm"
+web.release.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.nothreads.wasm"
 
 [dependencies]
 ios.debug = {

+ 6 - 0
test/project/main.gd

@@ -257,6 +257,12 @@ func _ready():
 	assert_equal(example_child.get_value1(), 11)
 	assert_equal(example_child.get_value2(), 22)
 
+	# Test that the extension's library path is absolute and valid.
+	var library_path = Example.test_library_path()
+	assert_equal(library_path.begins_with("res://"), false)
+	assert_equal(library_path, ProjectSettings.globalize_path(library_path))
+	assert_equal(FileAccess.file_exists(library_path), true)
+
 	exit_with_status()
 
 func _on_Example_custom_signal(signal_name, value):

+ 8 - 0
test/src/example.cpp

@@ -238,6 +238,8 @@ void Example::_bind_methods() {
 	ClassDB::bind_static_method("Example", D_METHOD("test_static", "a", "b"), &Example::test_static);
 	ClassDB::bind_static_method("Example", D_METHOD("test_static2"), &Example::test_static2);
 
+	ClassDB::bind_static_method("Example", D_METHOD("test_library_path"), &Example::test_library_path);
+
 	{
 		MethodInfo mi;
 		mi.arguments.push_back(PropertyInfo(Variant::STRING, "some_argument"));
@@ -675,3 +677,9 @@ void ExampleChild::_notification(int p_what) {
 String Example::test_use_engine_singleton() const {
 	return OS::get_singleton()->get_name();
 }
+
+String Example::test_library_path() {
+	String library_path;
+	internal::gdextension_interface_get_library_path(internal::library, library_path._native_ptr());
+	return library_path;
+}

+ 2 - 0
test/src/example.h

@@ -185,6 +185,8 @@ public:
 	virtual void _input(const Ref<InputEvent> &event) override;
 
 	String test_use_engine_singleton() const;
+
+	static String test_library_path();
 };
 
 VARIANT_ENUM_CAST(Example::Constants);

+ 7 - 0
tools/godotcpp.py

@@ -267,6 +267,8 @@ def options(opts, env):
         )
     )
 
+    opts.Add(BoolVariable(key="threads", help="Enable threading support", default=env.get("threads", True)))
+
     # compiledb
     opts.Add(
         BoolVariable(
@@ -403,6 +405,9 @@ def generate(env):
 
     tool.generate(env)
 
+    if env["threads"]:
+        env.Append(CPPDEFINES=["THREADS_ENABLED"])
+
     if env.use_hot_reload:
         env.Append(CPPDEFINES=["HOT_RELOAD_ENABLED"])
 
@@ -446,6 +451,8 @@ def generate(env):
     suffix += "." + env["arch"]
     if env["ios_simulator"]:
         suffix += ".simulator"
+    if not env["threads"]:
+        suffix += ".nothreads"
 
     env["suffix"] = suffix  # Exposed when included from another project
     env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"]

+ 3 - 13
tools/ios.py

@@ -1,3 +1,4 @@
+import codecs
 import os
 import subprocess
 import sys
@@ -5,17 +6,6 @@ import sys
 import common_compiler_flags
 from SCons.Variables import BoolVariable
 
-if sys.version_info < (3,):
-
-    def decode_utf8(x):
-        return x
-
-else:
-    import codecs
-
-    def decode_utf8(x):
-        return codecs.utf_8_decode(x)[0]
-
 
 def has_ios_osxcross():
     return "OSXCROSS_IOS" in os.environ
@@ -53,9 +43,9 @@ def generate(env):
     if sys.platform == "darwin":
         if env["IOS_SDK_PATH"] == "":
             try:
-                env["IOS_SDK_PATH"] = decode_utf8(
+                env["IOS_SDK_PATH"] = codecs.utf_8_decode(
                     subprocess.check_output(["xcrun", "--sdk", sdk_name, "--show-sdk-path"]).strip()
-                )
+                )[0]
             except (subprocess.CalledProcessError, OSError):
                 raise ValueError(
                     "Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name)

+ 9 - 4
tools/web.py

@@ -34,12 +34,17 @@ def generate(env):
     env["SHLIBSUFFIX"] = ".wasm"
 
     # Thread support (via SharedArrayBuffer).
-    env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"])
-    env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"])
+    if env["threads"]:
+        env.Append(CCFLAGS=["-sUSE_PTHREADS=1"])
+        env.Append(LINKFLAGS=["-sUSE_PTHREADS=1"])
 
     # Build as side module (shared library).
-    env.Append(CPPFLAGS=["-s", "SIDE_MODULE=1"])
-    env.Append(LINKFLAGS=["-s", "SIDE_MODULE=1"])
+    env.Append(CCFLAGS=["-sSIDE_MODULE=1"])
+    env.Append(LINKFLAGS=["-sSIDE_MODULE=1"])
+
+    # Force wasm longjmp mode.
+    env.Append(CCFLAGS=["-sSUPPORT_LONGJMP='wasm'"])
+    env.Append(LINKFLAGS=["-sSUPPORT_LONGJMP='wasm'"])
 
     env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"])