diff --git a/.github/actions/godot-cache/action.yml b/.github/actions/godot-cache-restore/action.yml similarity index 77% rename from .github/actions/godot-cache/action.yml rename to .github/actions/godot-cache-restore/action.yml index 2d7afc851..5df577656 100644 --- a/.github/actions/godot-cache/action.yml +++ b/.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}} diff --git a/.github/actions/godot-cache-save/action.yml b/.github/actions/godot-cache-save/action.yml new file mode 100644 index 000000000..b7cbf91f9 --- /dev/null +++ b/.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}} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1012d8a5..02ee1dd65 100644 --- a/.github/workflows/ci.yml +++ b/.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' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 7884a06b9..96090611d 100644 --- a/CMakeLists.txt +++ b/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) diff --git a/binding_generator.py b/binding_generator.py index d7bce6025..bd62ed88a 100644 --- a/binding_generator.py +++ b/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 ") + 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 ") result.append("#include ") result.append("#include ") - result.append("#include ") + result.append("") if class_name == "PackedStringArray": result.append("#include ") + result.append("") if class_name == "PackedColorArray": result.append("#include ") + result.append("") if class_name == "PackedVector2Array": result.append("#include ") + result.append("") if class_name == "PackedVector3Array": result.append("#include ") + result.append("") if is_packed_array(class_name): result.append("#include ") result.append("#include ") + result.append("") if class_name == "Array": result.append("#include ") + result.append("") if class_name == "Callable": result.append("#include ") - - for include in fully_used_classes: - if include == "TypedArray": - result.append("#include ") - else: - result.append(f"#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 ") @@ -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(&opaque); }}" + f"\t_FORCE_INLINE_ GDExtensionTypePtr _native_ptr() const {{ return const_cast(&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 ") result.append("") result.append("#include ") @@ -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 ") - 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 ") @@ -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 ") + 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 ") + 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 ") - else: - result.append(f"#include ") + 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 ") - - 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) {{" + f"\t\tif constexpr (!std::is_same_v) {{" ) 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 ") 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(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 ") result.append("") - for included in used_classes: - result.append(f"#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("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 ") source.append("") - source.append("#include ") source.append("#include ") + source.append("#include ") 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 " + result.append("template ") 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_args {{ " + args_array = f"\tstd::array 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" diff --git a/include/godot_cpp/classes/wrapped.hpp b/include/godot_cpp/classes/wrapped.hpp index 0f9da9934..293341723 100644 --- a/include/godot_cpp/classes/wrapped.hpp +++ b/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(p_instance); \ return cls->_set(*reinterpret_cast(p_name), *reinterpret_cast(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(p_instance); \ return cls->_get(*reinterpret_cast(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(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; \ } \ \ diff --git a/include/godot_cpp/templates/safe_refcount.hpp b/include/godot_cpp/templates/safe_refcount.hpp index 12e6840ae..98cb04b20 100644 --- a/include/godot_cpp/templates/safe_refcount.hpp +++ b/include/godot_cpp/templates/safe_refcount.hpp @@ -132,7 +132,7 @@ class SafeNumeric { } } - _ALWAYS_INLINE_ explicit SafeNumeric(T p_value = static_cast(0)) { + _ALWAYS_INLINE_ explicit SafeNumeric(T p_value = static_cast(0)) { set(p_value); } }; diff --git a/test/project/example.gdextension b/test/project/example.gdextension index 4f599ced2..8e2f794d1 100644 --- a/test/project/example.gdextension +++ b/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 = { diff --git a/test/project/main.gd b/test/project/main.gd index 20c6e8381..a41b8ea83 100644 --- a/test/project/main.gd +++ b/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): diff --git a/test/src/example.cpp b/test/src/example.cpp index faa9edddb..b64ffbb6d 100644 --- a/test/src/example.cpp +++ b/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; +} diff --git a/test/src/example.h b/test/src/example.h index 771eefb74..a40e99062 100644 --- a/test/src/example.h +++ b/test/src/example.h @@ -185,6 +185,8 @@ class Example : public Control { virtual void _input(const Ref &event) override; String test_use_engine_singleton() const; + + static String test_library_path(); }; VARIANT_ENUM_CAST(Example::Constants); diff --git a/tools/godotcpp.py b/tools/godotcpp.py index 87a426c6d..c2af52677 100644 --- a/tools/godotcpp.py +++ b/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"] diff --git a/tools/ios.py b/tools/ios.py index 7240a97aa..9675ab1ac 100644 --- a/tools/ios.py +++ b/tools/ios.py @@ -1,3 +1,4 @@ +import codecs import os import subprocess import sys @@ -5,17 +6,6 @@ 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) diff --git a/tools/web.py b/tools/web.py index 79b4b24ce..c8f07c555 100644 --- a/tools/web.py +++ b/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"])