diff options
47 files changed, 1047 insertions, 818 deletions
diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 3ff70077c4..2506cb7227 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -19,28 +19,13 @@ jobs: - name: Install APT dependencies uses: awalsh128/cache-apt-pkgs-action@latest with: - packages: dos2unix libxml2-utils moreutils + packages: libxml2-utils - name: Install Python dependencies and general setup run: | - pip3 install pytest==7.1.2 mypy==0.971 + pip3 install pytest==7.1.2 git config diff.wsErrorHighlight all - - name: Get changed files - id: changed-files - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if [ "${{ github.event_name }}" == "pull_request" ]; then - files=$(git diff-tree --no-commit-id --name-only -r HEAD^1..HEAD 2> /dev/null || true) - elif [ "${{ github.event_name }}" == "push" -a "${{ github.event.forced }}" == "false" -a "${{ github.event.created }}" == "false" ]; then - files=$(git diff-tree --no-commit-id --name-only -r ${{ github.event.before }}..${{ github.event.after }} 2> /dev/null || true) - fi - echo "$files" >> changed.txt - cat changed.txt - files=$(echo "$files" | grep -v 'thirdparty' | xargs -I {} sh -c 'echo "./{}"' | tr '\n' ' ') - echo "CHANGED_FILES=$files" >> $GITHUB_ENV - # This needs to happen before Python and npm execution; it must happen before any extra files are written. - name: .gitignore checks (gitignore_check.sh) run: | @@ -49,51 +34,12 @@ jobs: - name: Style checks via pre-commit uses: pre-commit/action@v3.0.1 with: - extra_args: --verbose --files ${{ env.CHANGED_FILES }} - - - name: File formatting checks (file_format.sh) - run: | - bash ./misc/scripts/file_format.sh changed.txt - - - name: Header guards formatting checks (header_guards.sh) - run: | - bash ./misc/scripts/header_guards.sh changed.txt - - - name: Python scripts static analysis (mypy_check.sh) - run: | - if grep -qE '\.py$|SConstruct|SCsub' changed.txt || [ -z "$(cat changed.txt)" ]; then - bash ./misc/scripts/mypy_check.sh - else - echo "Skipping Python static analysis as no Python files were changed." - fi - - - name: Python builders checks via pytest (pytest_builders.sh) - run: | - bash ./misc/scripts/pytest_builders.sh + extra_args: --verbose - - name: JavaScript style and documentation checks via ESLint and JSDoc + - name: Python builders checks via pytest run: | - if grep -q "\.js" changed.txt || [ -z "$(cat changed.txt)" ]; then - cd platform/web - npm ci - npm run lint - npm run docs -- -d dry-run - else - echo "Skipping JavaScript formatting as no Web/JS files were changed." - fi + pytest ./tests/python_build - name: Class reference schema checks run: | xmllint --noout --schema doc/class.xsd doc/classes/*.xml modules/*/doc_classes/*.xml platform/*/doc_classes/*.xml - - - name: Documentation checks - run: | - doc/tools/doc_status.py doc/classes modules/*/doc_classes platform/*/doc_classes - - - name: Spell checks via codespell - if: github.event_name == 'pull_request' && env.CHANGED_FILES != '' - uses: codespell-project/actions-codespell@v2 - with: - skip: "./bin,./thirdparty,*.desktop,*.gen.*,*.po,*.pot,*.rc,./AUTHORS.md,./COPYRIGHT.txt,./DONORS.md,./core/input/gamecontrollerdb.txt,./core/string/locales.h,./editor/project_converter_3_to_4.cpp,./misc/scripts/codespell.sh,./platform/android/java/lib/src/com,./platform/web/node_modules,./platform/web/package-lock.json" - ignore_words_list: "breaked,colour,curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,requestor,te,thirdparty,vai" - path: ${{ env.CHANGED_FILES }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7122363f77..e59419ac08 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,9 @@ +exclude: | + (?x)^( + .*thirdparty/.*| + .*-so_wrap\.(h|c)$ + ) + repos: - repo: https://github.com/pre-commit/mirrors-clang-format rev: v17.0.6 @@ -7,10 +13,8 @@ repos: types_or: [text] exclude: | (?x)^( - tests/python_build.*| - .*thirdparty.*| - .*platform/android/java/lib/src/com.*| - .*-so_wrap.* + tests/python_build/.*| + platform/android/java/lib/src/com/.* ) - repo: https://github.com/psf/black-pre-commit-mirror @@ -19,33 +23,155 @@ repos: - id: black files: (\.py$|SConstruct|SCsub) types_or: [text] - exclude: .*thirdparty.* args: - --line-length=120 + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.971 + hooks: + - id: mypy + files: \.py$ + types_or: [text] + args: [--config-file=./misc/scripts/mypy.ini] + + - repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + types_or: [text] + exclude: | + (?x)^( + .*\.desktop$| + .*\.gitignore$| + .*\.po$| + .*\.pot$| + .*\.rc$| + \.mailmap$| + AUTHORS.md$| + COPYRIGHT.txt$| + DONORS.md$| + core/input/gamecontrollerdb.txt$| + core/string/locales.h$| + editor/project_converter_3_to_4.cpp$| + platform/android/java/lib/src/com/.*| + platform/web/package-lock.json$ + ) + args: + - --enable-colors + - --write-changes + - --check-hidden + - --quiet-level + - '3' + - --ignore-words-list + - aesthetic,aesthetics,breaked,cancelled,colour,curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,requestor,te,thirdparty,vai + - --builtin + - clear,rare,en-GB_to_en-US + + ### Requires Docker; look into alternative implementation. + # - repo: https://github.com/comkieffer/pre-commit-xmllint.git + # rev: 1.0.0 + # hooks: + # - id: xmllint + # language: docker + # types_or: [text] + # files: ^(doc/classes|.*/doc_classes)/.*\.xml$ + # args: [--schema, doc/class.xsd] + + - repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.46.0 + hooks: + - id: eslint + name: eslint-engine + files: ^(platform/web/js/engine/|js/jsdoc2rst/).*\.js$ + args: [--fix, --no-eslintrc, --config, platform/web/.eslintrc.engine.js] + additional_dependencies: &eslint-deps + - eslint@8.46.0 + - eslint-config-airbnb-base@15.0.0 + - eslint-plugin-import@2.28.0 + - eslint-plugin-html@7.1.0 + - '@html-eslint/eslint-plugin@0.19.1' + - '@html-eslint/parser@0.19.1' + - id: eslint + name: eslint-libs + files: ^(platform/web/js/libs/|modules/).*\.js$ + args: [--fix, --no-eslintrc, --config, platform/web/.eslintrc.libs.js] + additional_dependencies: *eslint-deps + - id: eslint + name: eslint-sw + files: ^misc/dist/html/service-worker\.js$ + args: [--fix, --no-eslintrc, --config, platform/web/.eslintrc.sw.js] + additional_dependencies: *eslint-deps + - id: eslint + name: eslint-html + files: ^misc/dist/html/.*\.html$ + types: [html] + args: [--fix, --no-eslintrc, --config, platform/web/.eslintrc.html.js] + additional_dependencies: *eslint-deps + - repo: local hooks: - id: make-rst name: make-rst + language: python entry: python3 doc/tools/make_rst.py doc/classes modules platform --dry-run --color pass_filenames: false + files: ^(doc/classes|.*/doc_classes)/.*\.xml$ + + - id: doc-status + name: doc-status language: python - files: ^(doc|modules|platform).*xml$ + entry: python3 doc/tools/doc_status.py + files: ^(doc/classes|.*/doc_classes)/.*\.xml$ + + - id: jsdoc + name: jsdoc + language: node + entry: jsdoc + files: ^platform/web/js/engine/(engine|config|features)\.js$ + args: [--template, platform/web/js/jsdoc2rst/, --destination, '', -d, dry-run] + additional_dependencies: ["jsdoc@4.0.2"] - id: copyright-headers name: copyright-headers language: python - files: \.(c|h|cpp|hpp|cc|cxx|m|mm|inc|java)$ entry: python3 misc/scripts/copyright_headers.py + files: \.(c|h|cpp|hpp|cc|cxx|m|mm|inc|java)$ exclude: | (?x)^( - .*thirdparty.*| - .*-so_wrap.*| core/math/bvh_.*\.inc$| - platform/android/java/lib/src/com.*| - platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.*| - platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.*| - platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.* + platform/android/java/lib/src/com/.*| + platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView\.java$| + platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper\.java$| + platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix\.java$ + ) + + - id: header-guards + name: header-guards + language: python + entry: python3 misc/scripts/header_guards.py + files: \.(h|hpp)$ + exclude: | + (?x)^( + .*/thread\.h$| + .*/platform_config\.h$| + .*/platform_gl\.$h + ) + + - id: file-format + name: file-format + language: python + entry: python3 misc/scripts/file_format.py + types_or: [text] + exclude: | + (?x)^( + .*\.test\.txt$| + .*\.svg$| + .*\.patch$| + .*\.out$| + modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines\.gd$| + modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment\.notest\.gd$| + modules/gdscript/tests/scripts/parser/warnings/empty_file_newline\.notest\.gd$| + platform/android/java/lib/src/com/google/.* ) - id: dotnet-format diff --git a/SConstruct b/SConstruct index 81ce4bca52..cfedcc84ed 100644 --- a/SConstruct +++ b/SConstruct @@ -61,7 +61,6 @@ _helper_module("platform_methods", "platform_methods.py") _helper_module("version", "version.py") _helper_module("core.core_builders", "core/core_builders.py") _helper_module("main.main_builders", "main/main_builders.py") -_helper_module("modules.modules_builders", "modules/modules_builders.py") # Local import methods @@ -69,7 +68,7 @@ import glsl_builders import gles3_builders import scu_builders from methods import print_warning, print_error -from platform_methods import architectures, architecture_aliases, generate_export_icons +from platform_methods import architectures, architecture_aliases if ARGUMENTS.get("target", "editor") == "editor": _helper_module("editor.editor_builders", "editor/editor_builders.py") @@ -107,7 +106,6 @@ for x in sorted(glob.glob("platform/*")): if os.path.exists(x + "/export/export.cpp"): platform_exporters.append(platform_name) - generate_export_icons(x, platform_name) if os.path.exists(x + "/api/api.cpp"): platform_apis.append(platform_name) if detect.can_build(): @@ -428,7 +426,7 @@ for name, path in modules_detected.items(): sys.path.remove(path) sys.modules.pop("config") -methods.write_modules(modules_detected) +env.modules_detected = modules_detected # Update the environment again after all the module options are added. opts.Update(env) @@ -544,7 +542,7 @@ env.Append(CFLAGS=env.get("cflags", "").split()) env.Append(LINKFLAGS=env.get("linkflags", "").split()) # Feature build profile -disabled_classes = [] +env.disabled_classes = [] if env["build_profile"] != "": print('Using feature build profile: "{}"'.format(env["build_profile"])) import json @@ -552,7 +550,7 @@ if env["build_profile"] != "": try: ft = json.load(open(env["build_profile"])) if "disabled_classes" in ft: - disabled_classes = ft["disabled_classes"] + env.disabled_classes = ft["disabled_classes"] if "disabled_build_options" in ft: dbo = ft["disabled_build_options"] for c in dbo: @@ -560,7 +558,6 @@ if env["build_profile"] != "": except: print_error('Failed to open feature build profile: "{}"'.format(env["build_profile"])) Exit(255) -methods.write_disabled_classes(disabled_classes) # Platform specific flags. # These can sometimes override default options. @@ -926,7 +923,7 @@ if env.editor_build: print_error("Not all modules required by editor builds are enabled.") Exit(255) -methods.generate_version_header(env.module_version_string) +env.version_info = methods.get_version_info(env.module_version_string) env["PROGSUFFIX_WRAP"] = suffix + env.module_version_string + ".console" + env["PROGSUFFIX"] env["PROGSUFFIX"] = suffix + env.module_version_string + env["PROGSUFFIX"] diff --git a/core/SCsub b/core/SCsub index 91620cb075..640c6de6a1 100644 --- a/core/SCsub +++ b/core/SCsub @@ -4,44 +4,9 @@ Import("env") import core_builders import methods - -env.core_sources = [] - - -# Generate AES256 script encryption key import os -txt = "0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0" -if "SCRIPT_AES256_ENCRYPTION_KEY" in os.environ: - key = os.environ["SCRIPT_AES256_ENCRYPTION_KEY"] - ec_valid = True - if len(key) != 64: - ec_valid = False - else: - txt = "" - for i in range(len(key) >> 1): - if i > 0: - txt += "," - txts = "0x" + key[i * 2 : i * 2 + 2] - try: - int(txts, 16) - except Exception: - ec_valid = False - txt += txts - if not ec_valid: - methods.print_error( - f'Invalid AES256 encryption key, not 64 hexadecimal characters: "{key}".\n' - "Unset 'SCRIPT_AES256_ENCRYPTION_KEY' in your environment " - "or make sure that it contains exactly 64 hexadecimal characters." - ) - Exit(255) - - -script_encryption_key_contents = ( - '#include "core/config/project_settings.h"\nuint8_t script_encryption_key[32]={' + txt + "};\n" -) - -methods.write_file_if_needed("script_encryption_key.gen.cpp", script_encryption_key_contents) +env.core_sources = [] # Add required thirdparty code. @@ -193,8 +158,96 @@ env.core_sources += thirdparty_obj # Godot source files env.add_source_files(env.core_sources, "*.cpp") -env.add_source_files(env.core_sources, "script_encryption_key.gen.cpp") -env.add_source_files(env.core_sources, "version_hash.gen.cpp") + + +# Generate disabled classes +def disabled_class_builder(target, source, env): + with methods.generated_wrapper(target) as file: + for c in source[0].read(): + cs = c.strip() + if cs != "": + file.write(f"#define ClassDB_Disable_{cs} 1") + + +env.CommandNoCache("disabled_classes.gen.h", env.Value(env.disabled_classes), env.Run(disabled_class_builder)) + + +# Generate version info +def version_info_builder(target, source, env): + with methods.generated_wrapper(target) as file: + file.write( + """\ +#define VERSION_SHORT_NAME "{short_name}" +#define VERSION_NAME "{name}" +#define VERSION_MAJOR {major} +#define VERSION_MINOR {minor} +#define VERSION_PATCH {patch} +#define VERSION_STATUS "{status}" +#define VERSION_BUILD "{build}" +#define VERSION_MODULE_CONFIG "{module_config}" +#define VERSION_WEBSITE "{website}" +#define VERSION_DOCS_BRANCH "{docs_branch}" +#define VERSION_DOCS_URL "https://docs.godotengine.org/en/" VERSION_DOCS_BRANCH +""".format( + **env.version_info + ) + ) + + +env.CommandNoCache("version_generated.gen.h", "#version.py", env.Run(version_info_builder)) + + +# Generate version hash +def version_hash_builder(target, source, env): + with methods.generated_wrapper(target) as file: + file.write( + """\ +#include "core/version.h" + +const char *const VERSION_HASH = "{git_hash}"; +const uint64_t VERSION_TIMESTAMP = {git_timestamp}; +""".format( + **env.version_info + ) + ) + + +gen_hash = env.CommandNoCache( + "version_hash.gen.cpp", env.Value(env.version_info["git_hash"]), env.Run(version_hash_builder) +) +env.add_source_files(env.core_sources, gen_hash) + + +# Generate AES256 script encryption key +def encryption_key_builder(target, source, env): + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +#include "core/config/project_settings.h" + +uint8_t script_encryption_key[32] = {{ + {source[0]} +}};""" + ) + + +gdkey = os.environ.get("SCRIPT_AES256_ENCRYPTION_KEY", "0" * 64) +ec_valid = len(gdkey) == 64 +if ec_valid: + try: + gdkey = ", ".join([str(int(f"{a}{b}", 16)) for a, b in zip(gdkey[0::2], gdkey[1::2])]) + except Exception: + ec_valid = False +if not ec_valid: + methods.print_error( + f'Invalid AES256 encryption key, not 64 hexadecimal characters: "{gdkey}".\n' + "Unset `SCRIPT_AES256_ENCRYPTION_KEY` in your environment " + "or make sure that it contains exactly 64 hexadecimal characters." + ) + Exit(255) +gen_encrypt = env.CommandNoCache("script_encryption_key.gen.cpp", env.Value(gdkey), env.Run(encryption_key_builder)) +env.add_source_files(env.core_sources, gen_encrypt) + # Certificates env.Depends( diff --git a/core/object/object.cpp b/core/object/object.cpp index ab89f96a0d..dfc8e2a29a 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -2307,9 +2307,9 @@ void ObjectDB::setup() { } void ObjectDB::cleanup() { - if (slot_count > 0) { - spin_lock.lock(); + spin_lock.lock(); + if (slot_count > 0) { WARN_PRINT("ObjectDB instances leaked at exit (run with --verbose for details)."); if (OS::get_singleton()->is_stdout_verbose()) { // Ensure calling the native classes because if a leaked instance has a script @@ -2340,10 +2340,11 @@ void ObjectDB::cleanup() { } print_line("Hint: Leaked instances typically happen when nodes are removed from the scene tree (with `remove_child()`) but not freed (with `free()` or `queue_free()`)."); } - spin_lock.unlock(); } if (object_slots) { memfree(object_slots); } + + spin_lock.unlock(); } diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h index bb36f38d54..dbf938a117 100644 --- a/core/templates/command_queue_mt.h +++ b/core/templates/command_queue_mt.h @@ -302,7 +302,7 @@ class CommandQueueMT { struct CommandBase { bool sync = false; virtual void call() = 0; - virtual ~CommandBase() = default; // Won't be called. + virtual ~CommandBase() = default; }; struct SyncCommand : public CommandBase { @@ -327,7 +327,6 @@ class CommandQueueMT { enum { DEFAULT_COMMAND_MEM_SIZE_KB = 256, - SYNC_SEMAPHORES = 8 }; BinaryMutex mutex; @@ -335,6 +334,7 @@ class CommandQueueMT { ConditionVariable sync_cond_var; uint32_t sync_head = 0; uint32_t sync_tail = 0; + uint32_t sync_awaiters = 0; WorkerThreadPool::TaskID pump_task_id = WorkerThreadPool::INVALID_TASK_ID; uint64_t flush_read_ptr = 0; @@ -349,6 +349,15 @@ class CommandQueueMT { return cmd; } + _FORCE_INLINE_ void _prevent_sync_wraparound() { + bool safe_to_reset = !sync_awaiters; + bool already_sync_to_latest = sync_head == sync_tail; + if (safe_to_reset && already_sync_to_latest) { + sync_head = 0; + sync_tail = 0; + } + } + void _flush() { if (unlikely(flush_read_ptr)) { // Re-entrant call. @@ -365,25 +374,39 @@ class CommandQueueMT { cmd->call(); if (unlikely(cmd->sync)) { sync_head++; + unlock(); // Give an opportunity to awaiters right away. sync_cond_var.notify_all(); + lock(); } + // If the command involved reallocating the buffer, the address may have changed. + cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); + cmd->~CommandBase(); + flush_read_ptr += size; } WorkerThreadPool::thread_exit_command_queue_mt_flush(); command_mem.clear(); flush_read_ptr = 0; + + _prevent_sync_wraparound(); + unlock(); } _FORCE_INLINE_ void _wait_for_sync(MutexLock<BinaryMutex> &p_lock) { + sync_awaiters++; uint32_t sync_head_goal = sync_tail; do { sync_cond_var.wait(p_lock); - } while (sync_head != sync_head_goal); // Can't use lower-than because of wraparound. + } while (sync_head < sync_head_goal); + sync_awaiters--; + _prevent_sync_wraparound(); } + void _no_op() {} + public: void lock(); void unlock(); @@ -405,10 +428,15 @@ public: _flush(); } } + void flush_all() { _flush(); } + void sync() { + push_and_sync(this, &CommandQueueMT::_no_op); + } + void wait_and_flush() { ERR_FAIL_COND(pump_task_id == WorkerThreadPool::INVALID_TASK_ID); WorkerThreadPool::get_singleton()->wait_for_task_completion(pump_task_id); @@ -416,7 +444,9 @@ public: } void set_pump_task_id(WorkerThreadPool::TaskID p_task_id) { + lock(); pump_task_id = p_task_id; + unlock(); } CommandQueueMT(); diff --git a/doc/classes/CryptoKey.xml b/doc/classes/CryptoKey.xml index ff826a3ae5..dd128b6806 100644 --- a/doc/classes/CryptoKey.xml +++ b/doc/classes/CryptoKey.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CryptoKey" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A cryptographic key (RSA). + A cryptographic key (RSA or elliptic-curve). </brief_description> <description> The CryptoKey class represents a cryptographic key. Keys can be loaded and saved like any other [Resource]. diff --git a/doc/classes/SkeletonModifier3D.xml b/doc/classes/SkeletonModifier3D.xml index fab33750ea..c0b1b6fd53 100644 --- a/doc/classes/SkeletonModifier3D.xml +++ b/doc/classes/SkeletonModifier3D.xml @@ -6,9 +6,19 @@ <description> [SkeletonModifier3D] retrieves a target [Skeleton3D] by having a [Skeleton3D] parent. If there is [AnimationMixer], modification always performs after playback process of the [AnimationMixer]. + This node should be used to implement custom IK solvers, constraints, or skeleton physics </description> <tutorials> </tutorials> + <methods> + <method name="_process_modification" qualifiers="virtual"> + <return type="void" /> + <description> + Override this virtual method to implement a custom skeleton modifier. You should do things like get the [Skeleton3D]'s current pose and apply the pose here. + [method _process_modification] must not apply [member influence] to bone poses because the [Skeleton3D] automatically applies influence to all bone poses set by the modifier. + </description> + </method> + </methods> <members> <member name="active" type="bool" setter="set_active" getter="is_active" default="true"> If [code]true[/code], the [SkeletonModifier3D] will be processing. diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 767a394ce5..6e7d4a6733 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -395,10 +395,13 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); if (p_first) { - Size2i win_size = DisplayServer::get_singleton()->window_get_size(); if (p_screen_rect.position != Vector2() || p_screen_rect.size != rt->size) { // Viewport doesn't cover entire window so clear window to black before blitting. - glViewport(0, 0, win_size.width, win_size.height); + // Querying the actual window size from the DisplayServer would deadlock in separate render thread mode, + // so let's set the biggest viewport the implementation supports, to be sure the window is fully covered. + GLsizei max_vp[2] = {}; + glGetIntegerv(GL_MAX_VIEWPORT_DIMS, max_vp); + glViewport(0, 0, max_vp[0], max_vp[1]); glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); } diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 373df8d8de..a65347a5b1 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -2312,9 +2312,11 @@ void TextureStorage::_clear_render_target(RenderTarget *rt) { if (rt->backbuffer_fbo != 0) { glDeleteFramebuffers(1, &rt->backbuffer_fbo); + rt->backbuffer_fbo = 0; + } + if (rt->backbuffer != 0) { GLES3::Utilities::get_singleton()->texture_free_data(rt->backbuffer); rt->backbuffer = 0; - rt->backbuffer_fbo = 0; } if (rt->backbuffer_depth != 0) { GLES3::Utilities::get_singleton()->texture_free_data(rt->backbuffer_depth); diff --git a/editor/SCsub b/editor/SCsub index e3b17b83f8..e613a71238 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -10,40 +10,59 @@ import editor_builders import methods -def _make_doc_data_class_path(to_path): - file_path = os.path.join(to_path, "doc_data_class_path.gen.h") - - class_path_data = "" - class_path_data += "static const int _doc_data_class_path_count = " + str(len(env.doc_class_path)) + ";\n" - class_path_data += "struct _DocDataClassPath { const char* name; const char* path; };\n" - class_path_data += ( - "static const _DocDataClassPath _doc_data_class_paths[" + str(len(env.doc_class_path) + 1) + "] = {\n" - ) - for c in sorted(env.doc_class_path): - class_path_data += '\t{"' + c + '", "' + env.doc_class_path[c] + '"},\n' - class_path_data += "\t{nullptr, nullptr}\n" - class_path_data += "};\n" - - methods.write_file_if_needed(file_path, class_path_data) - - if env.editor_build: + # Generate doc data paths + def doc_data_class_path_builder(target, source, env): + paths = dict(sorted(source[0].read().items())) + data = "\n".join([f'\t{{"{key}", "{value}"}},' for key, value in paths.items()]) + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +static const int _doc_data_class_path_count = {len(paths)}; + +struct _DocDataClassPath {{ + const char *name; + const char *path; +}}; + +static const _DocDataClassPath _doc_data_class_paths[{len(env.doc_class_path) + 1}] = {{ +{data} + {{nullptr, nullptr}}, +}}; +""" + ) + + env.CommandNoCache("doc_data_class_path.gen.h", env.Value(env.doc_class_path), env.Run(doc_data_class_path_builder)) + # Register exporters - reg_exporters_inc = '#include "register_exporters.h"\n\n' - reg_exporters = "void register_exporters() {\n" + def register_exporters_builder(target, source, env): + platforms = source[0].read() + exp_inc = "\n".join([f'#include "platform/{p}/export/export.h"' for p in platforms]) + exp_reg = "\n".join([f"\tregister_{p}_exporter();" for p in platforms]) + exp_type = "\n".join([f"\tregister_{p}_exporter_types();" for p in platforms]) + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +#include "register_exporters.h" + +{exp_inc} + +void register_exporters() {{ +{exp_reg} +}} + +void register_exporter_types() {{ +{exp_type} +}} +""" + ) + + gen_exporters = env.CommandNoCache( + "register_exporters.gen.cpp", env.Value(env.platform_exporters), env.Run(register_exporters_builder) + ) for e in env.platform_exporters: # Add all .cpp files in export folder - env.add_source_files(env.editor_sources, "../platform/" + e + "/export/" + "*.cpp") - - reg_exporters += "\tregister_" + e + "_exporter();\n" - reg_exporters_inc += '#include "platform/' + e + '/export/export.h"\n' - reg_exporters += "}\n\n" - reg_exporters += "void register_exporter_types() {\n" - for e in env.platform_exporters: - reg_exporters += "\tregister_" + e + "_exporter_types();\n" - reg_exporters += "}\n" - - methods.write_file_if_needed("register_exporters.gen.cpp", reg_exporters_inc + reg_exporters) + env.add_source_files(env.editor_sources, f"../platform/{e}/export/*.cpp") # Core API documentation. docs = [] @@ -61,8 +80,6 @@ if env.editor_build: else: docs += Glob(d + "/*.xml") # Custom. - _make_doc_data_class_path(env.Dir("#editor").abspath) - docs = sorted(docs) env.Depends("#editor/doc_data_compressed.gen.h", docs) env.CommandNoCache( @@ -115,7 +132,7 @@ if env.editor_build: ) env.add_source_files(env.editor_sources, "*.cpp") - env.add_source_files(env.editor_sources, "register_exporters.gen.cpp") + env.add_source_files(env.editor_sources, gen_exporters) SConscript("debugger/SCsub") SConscript("export/SCsub") diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 3f86d30368..de56767929 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -912,7 +912,9 @@ void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const R Color color = instantiated ? instantiated_color : p_albedo; if (!selected) { - color.a *= 0.85; + color.r *= 0.6; + color.g *= 0.6; + color.b *= 0.6; } icon->set_albedo(color); @@ -921,9 +923,8 @@ void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const R icon->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); icon->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); icon->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); - icon->set_cull_mode(StandardMaterial3D::CULL_DISABLED); - icon->set_depth_draw_mode(StandardMaterial3D::DEPTH_DRAW_DISABLED); - icon->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + icon->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA_SCISSOR); + icon->set_alpha_scissor_threshold(0.1); icon->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, p_texture); icon->set_flag(StandardMaterial3D::FLAG_FIXED_SIZE, true); icon->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED); diff --git a/editor/themes/editor_color_map.cpp b/editor/themes/editor_color_map.cpp index 99bcf109d0..9046a8b688 100644 --- a/editor/themes/editor_color_map.cpp +++ b/editor/themes/editor_color_map.cpp @@ -169,9 +169,7 @@ void EditorColorMap::create() { add_conversion_exception("ZoomReset"); add_conversion_exception("LockViewport"); add_conversion_exception("GroupViewport"); - add_conversion_exception("StatusError"); add_conversion_exception("StatusSuccess"); - add_conversion_exception("StatusWarning"); add_conversion_exception("OverbrightIndicator"); add_conversion_exception("MaterialPreviewCube"); add_conversion_exception("MaterialPreviewSphere"); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index ee008e5636..2ef62c60a2 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -436,8 +436,8 @@ void EditorThemeManager::_create_shared_styles(const Ref<EditorTheme> &p_theme, if (!p_config.dark_theme) { // Darken some colors to be readable on a light background. p_config.success_color = p_config.success_color.lerp(p_config.mono_color, 0.35); - p_config.warning_color = p_config.warning_color.lerp(p_config.mono_color, 0.35); - p_config.error_color = p_config.error_color.lerp(p_config.mono_color, 0.25); + p_config.warning_color = Color(0.82, 0.56, 0.1); + p_config.error_color = Color(0.8, 0.22, 0.22); } p_theme->set_color("mono_color", EditorStringName(Editor), p_config.mono_color); @@ -1901,14 +1901,20 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme // When pressed, don't tint the icons with the accent color, just leave them normal. p_theme->set_color("icon_pressed_color", "EditorLogFilterButton", p_config.icon_normal_color); // When unpressed, dim the icons. - p_theme->set_color("icon_normal_color", "EditorLogFilterButton", p_config.icon_disabled_color); + Color icon_normal_color = Color(p_config.icon_normal_color, (p_config.dark_theme ? 0.4 : 0.8)); + p_theme->set_color("icon_normal_color", "EditorLogFilterButton", icon_normal_color); + Color icon_hover_color = p_config.icon_normal_color * (p_config.dark_theme ? 1.15 : 1.0); + icon_hover_color.a = 1.0; + p_theme->set_color("icon_hover_color", "EditorLogFilterButton", icon_hover_color); // When pressed, add a small bottom border to the buttons to better show their active state, // similar to active tabs. Ref<StyleBoxFlat> editor_log_button_pressed = style_flat_button_pressed->duplicate(); editor_log_button_pressed->set_border_width(SIDE_BOTTOM, 2 * EDSCALE); editor_log_button_pressed->set_border_color(p_config.accent_color); - + if (!p_config.dark_theme) { + editor_log_button_pressed->set_bg_color(flat_pressed_color.lightened(0.5)); + } p_theme->set_stylebox("normal", "EditorLogFilterButton", style_flat_button); p_theme->set_stylebox("hover", "EditorLogFilterButton", style_flat_button_hover); p_theme->set_stylebox("pressed", "EditorLogFilterButton", editor_log_button_pressed); diff --git a/methods.py b/methods.py index 0c29632f10..da221cc0ea 100644 --- a/methods.py +++ b/methods.py @@ -3,13 +3,16 @@ import sys import re import glob import subprocess +import contextlib from collections import OrderedDict from collections.abc import Mapping from enum import Enum -from typing import Iterator +from typing import Generator, Optional +from io import TextIOWrapper, StringIO from pathlib import Path from os.path import normpath, basename + # Get the "Godot" folder name ahead of time base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/" base_folder_only = os.path.basename(os.path.normpath(base_folder_path)) @@ -277,79 +280,6 @@ def get_version_info(module_version_string="", silent=False): return version_info -_cleanup_env = None -_cleanup_bool = False - - -def write_file_if_needed(path, string): - """Generates a file only if it doesn't already exist or the content has changed. - - Utilizes a dedicated SCons environment to ensure the files are properly removed - during cleanup; will not attempt to create files during cleanup. - - - `path` - Path to the file in question; used to create cleanup logic. - - `string` - Content to compare against an existing file. - """ - global _cleanup_env - global _cleanup_bool - - if _cleanup_env is None: - from SCons.Environment import Environment - - _cleanup_env = Environment() - _cleanup_bool = _cleanup_env.GetOption("clean") - - _cleanup_env.Clean("#", path) - if _cleanup_bool: - return - - try: - with open(path, "r", encoding="utf-8", newline="\n") as f: - if f.read() == string: - return - except FileNotFoundError: - pass - - with open(path, "w", encoding="utf-8", newline="\n") as f: - f.write(string) - - -def generate_version_header(module_version_string=""): - version_info = get_version_info(module_version_string) - - version_info_header = """\ -/* THIS FILE IS GENERATED DO NOT EDIT */ -#ifndef VERSION_GENERATED_GEN_H -#define VERSION_GENERATED_GEN_H -#define VERSION_SHORT_NAME "{short_name}" -#define VERSION_NAME "{name}" -#define VERSION_MAJOR {major} -#define VERSION_MINOR {minor} -#define VERSION_PATCH {patch} -#define VERSION_STATUS "{status}" -#define VERSION_BUILD "{build}" -#define VERSION_MODULE_CONFIG "{module_config}" -#define VERSION_WEBSITE "{website}" -#define VERSION_DOCS_BRANCH "{docs_branch}" -#define VERSION_DOCS_URL "https://docs.godotengine.org/en/" VERSION_DOCS_BRANCH -#endif // VERSION_GENERATED_GEN_H -""".format( - **version_info - ) - - version_hash_data = """\ -/* THIS FILE IS GENERATED DO NOT EDIT */ -#include "core/version.h" -const char *const VERSION_HASH = "{git_hash}"; -const uint64_t VERSION_TIMESTAMP = {git_timestamp}; -""".format( - **version_info - ) - - write_file_if_needed("core/version_generated.gen.h", version_info_header) - write_file_if_needed("core/version_hash.gen.cpp", version_hash_data) - - def parse_cg_file(fname, uniforms, sizes, conditionals): with open(fname, "r", encoding="utf-8") as fs: line = fs.readline() @@ -465,63 +395,6 @@ def is_module(path): return True -def write_disabled_classes(class_list): - file_contents = "" - - file_contents += "/* THIS FILE IS GENERATED DO NOT EDIT */\n" - file_contents += "#ifndef DISABLED_CLASSES_GEN_H\n" - file_contents += "#define DISABLED_CLASSES_GEN_H\n\n" - for c in class_list: - cs = c.strip() - if cs != "": - file_contents += "#define ClassDB_Disable_" + cs + " 1\n" - file_contents += "\n#endif\n" - - write_file_if_needed("core/disabled_classes.gen.h", file_contents) - - -def write_modules(modules): - includes_cpp = "" - initialize_cpp = "" - uninitialize_cpp = "" - - for name, path in modules.items(): - try: - with open(os.path.join(path, "register_types.h")): - includes_cpp += '#include "' + path + '/register_types.h"\n' - initialize_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n" - initialize_cpp += "\tinitialize_" + name + "_module(p_level);\n" - initialize_cpp += "#endif\n" - uninitialize_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n" - uninitialize_cpp += "\tuninitialize_" + name + "_module(p_level);\n" - uninitialize_cpp += "#endif\n" - except OSError: - pass - - modules_cpp = """// register_module_types.gen.cpp -/* THIS FILE IS GENERATED DO NOT EDIT */ -#include "register_module_types.h" - -#include "modules/modules_enabled.gen.h" - -%s - -void initialize_modules(ModuleInitializationLevel p_level) { -%s -} - -void uninitialize_modules(ModuleInitializationLevel p_level) { -%s -} -""" % ( - includes_cpp, - initialize_cpp, - uninitialize_cpp, - ) - - write_file_if_needed("modules/register_module_types.gen.cpp", modules_cpp) - - def convert_custom_modules_path(path): if not path: return path @@ -1649,3 +1522,112 @@ def generate_vs_project(env, original_args, project_name="godot"): if get_bool(original_args, "vsproj_gen_only", True): sys.exit() + + +def generate_copyright_header(filename: str) -> str: + MARGIN = 70 + TEMPLATE = """\ +/**************************************************************************/ +/* %s*/ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ +""" + filename = filename.split("/")[-1].ljust(MARGIN) + if len(filename) > MARGIN: + print(f'WARNING: Filename "{filename}" too large for copyright header.') + return TEMPLATE % filename + + +@contextlib.contextmanager +def generated_wrapper( + path, # FIXME: type with `Union[str, Node, List[Node]]` when pytest conflicts are resolved + guard: Optional[bool] = None, + prefix: str = "", + suffix: str = "", +) -> Generator[TextIOWrapper, None, None]: + """ + Wrapper class to automatically handle copyright headers and header guards + for generated scripts. Meant to be invoked via `with` statement similar to + creating a file. + + - `path`: The path of the file to be created. Can be passed a raw string, an + isolated SCons target, or a full SCons target list. If a target list contains + multiple entries, produces a warning & only creates the first entry. + - `guard`: Optional bool to determine if a header guard should be added. If + unassigned, header guards are determined by the file extension. + - `prefix`: Custom prefix to prepend to a header guard. Produces a warning if + provided a value when `guard` evaluates to `False`. + - `suffix`: Custom suffix to append to a header guard. Produces a warning if + provided a value when `guard` evaluates to `False`. + """ + + # Handle unfiltered SCons target[s] passed as path. + if not isinstance(path, str): + if isinstance(path, list): + if len(path) > 1: + print_warning( + "Attempting to use generated wrapper with multiple targets; " + f"will only use first entry: {path[0]}" + ) + path = path[0] + if not hasattr(path, "get_abspath"): + raise TypeError(f'Expected type "str", "Node" or "List[Node]"; was passed {type(path)}.') + path = path.get_abspath() + + path = str(path).replace("\\", "/") + if guard is None: + guard = path.endswith((".h", ".hh", ".hpp", ".inc")) + if not guard and (prefix or suffix): + print_warning(f'Trying to assign header guard prefix/suffix while `guard` is disabled: "{path}".') + + header_guard = "" + if guard: + if prefix: + prefix += "_" + if suffix: + suffix = f"_{suffix}" + split = path.split("/")[-1].split(".") + header_guard = (f"{prefix}{split[0]}{suffix}.{'.'.join(split[1:])}".upper() + .replace(".", "_").replace("-", "_").replace(" ", "_").replace("__", "_")) # fmt: skip + + with open(path, "wt", encoding="utf-8", newline="\n") as file: + file.write(generate_copyright_header(path)) + file.write("\n/* THIS FILE IS GENERATED. EDITS WILL BE LOST. */\n\n") + + if guard: + file.write(f"#ifndef {header_guard}\n") + file.write(f"#define {header_guard}\n\n") + + with StringIO(newline="\n") as str_io: + yield str_io + file.write(str_io.getvalue().strip() or "/* NO CONTENT */") + + if guard: + file.write(f"\n\n#endif // {header_guard}") + + file.write("\n") diff --git a/misc/scripts/codespell.sh b/misc/scripts/codespell.sh deleted file mode 100755 index ef361a6495..0000000000 --- a/misc/scripts/codespell.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -SKIP_LIST="./.*,./**/.*,./bin,./thirdparty,*.desktop,*.gen.*,*.po,*.pot,*.rc,./AUTHORS.md,./COPYRIGHT.txt,./DONORS.md," -SKIP_LIST+="./core/input/gamecontrollerdb.txt,./core/string/locales.h,./editor/renames_map_3_to_4.cpp,./misc/scripts/codespell.sh," -SKIP_LIST+="./platform/android/java/lib/src/com,./platform/web/node_modules,./platform/web/package-lock.json," - -IGNORE_LIST="breaked,cancelled,colour,curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,requestor,te,thirdparty,vai" - -codespell -w -q 3 -S "${SKIP_LIST}" -L "${IGNORE_LIST}" --builtin "clear,rare,en-GB_to_en-US" diff --git a/misc/scripts/dotnet_format.py b/misc/scripts/dotnet_format.py index 83265be7c5..51fd7a1223 100644 --- a/misc/scripts/dotnet_format.py +++ b/misc/scripts/dotnet_format.py @@ -5,10 +5,16 @@ import glob import os import sys -# Create dummy generated files. +if len(sys.argv) < 2: + print("Invalid usage of dotnet_format.py, it should be called with a path to one or multiple files.") + sys.exit(1) + +# Create dummy generated files, if needed. for path in [ "modules/mono/SdkPackageVersions.props", ]: + if os.path.exists(path): + continue os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w", encoding="utf-8", newline="\n") as f: f.write("<Project />") @@ -17,14 +23,12 @@ for path in [ os.environ["GodotSkipGenerated"] = "true" # Match all the input files to their respective C# project. -input_files = [os.path.normpath(x) for x in sys.argv] projects = { - path: [f for f in sys.argv if os.path.commonpath([f, path]) == path] + path: " ".join([f for f in sys.argv[1:] if os.path.commonpath([f, path]) == path]) for path in [os.path.dirname(f) for f in glob.glob("**/*.csproj", recursive=True)] } # Run dotnet format on all projects with more than 0 modified files. for path, files in projects.items(): - if len(files) > 0: - command = f"dotnet format {path} --include {' '.join(files)}" - os.system(command) + if files: + os.system(f"dotnet format {path} --include {files}") diff --git a/misc/scripts/file_format.py b/misc/scripts/file_format.py new file mode 100644 index 0000000000..a4ea544a45 --- /dev/null +++ b/misc/scripts/file_format.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys + +if len(sys.argv) < 2: + print("Invalid usage of file_format.py, it should be called with a path to one or multiple files.") + sys.exit(1) + +BOM = b"\xef\xbb\xbf" + +changed = [] +invalid = [] + +for file in sys.argv[1:]: + try: + with open(file, "rt", encoding="utf-8") as f: + original = f.read() + except UnicodeDecodeError: + invalid.append(file) + continue + + if original == "": + continue + + EOL = "\r\n" if file.endswith((".csproj", ".sln", ".bat")) or file.startswith("misc/msvs") else "\n" + WANTS_BOM = file.endswith((".csproj", ".sln")) + + revamp = EOL.join([line.rstrip("\n\r\t ") for line in original.splitlines(True)]).rstrip(EOL) + EOL + + new_raw = revamp.encode(encoding="utf-8") + if not WANTS_BOM and new_raw.startswith(BOM): + new_raw = new_raw[len(BOM) :] + elif WANTS_BOM and not new_raw.startswith(BOM): + new_raw = BOM + new_raw + + with open(file, "rb") as f: + old_raw = f.read() + + if old_raw != new_raw: + changed.append(file) + with open(file, "wb") as f: + f.write(new_raw) + +if changed: + for file in changed: + print(f"FIXED: {file}") +if invalid: + for file in invalid: + print(f"REQUIRES MANUAL CHANGES: {file}") + sys.exit(1) diff --git a/misc/scripts/file_format.sh b/misc/scripts/file_format.sh deleted file mode 100755 index ad58657883..0000000000 --- a/misc/scripts/file_format.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env bash - -# This script ensures proper POSIX text file formatting and a few other things. -# This is supplementary to clang_format.sh and black_format.sh, but should be -# run before them. - -# We need dos2unix and isutf8. -if [ ! -x "$(command -v dos2unix)" -o ! -x "$(command -v isutf8)" ]; then - printf "Install 'dos2unix' and 'isutf8' (moreutils package) to use this script.\n" - exit 1 -fi - -set -uo pipefail - -if [ $# -eq 0 ]; then - # Loop through all code files tracked by Git. - mapfile -d '' files < <(git grep -zIl '') -else - # $1 should be a file listing file paths to process. Used in CI. - mapfile -d ' ' < <(cat "$1") -fi - -for f in "${files[@]}"; do - # Exclude some types of files. - if [[ "$f" == *"csproj" ]]; then - continue - elif [[ "$f" == *"sln" ]]; then - continue - elif [[ "$f" == *".bat" ]]; then - continue - elif [[ "$f" == *".out" ]]; then - # GDScript integration testing files. - continue - elif [[ "$f" == *"patch" ]]; then - continue - elif [[ "$f" == *"pot" ]]; then - continue - elif [[ "$f" == *"po" ]]; then - continue - elif [[ "$f" == "thirdparty/"* ]]; then - continue - elif [[ "$f" == *"/thirdparty/"* ]]; then - continue - elif [[ "$f" == "platform/android/java/lib/src/com/google"* ]]; then - continue - elif [[ "$f" == *"-so_wrap."* ]]; then - continue - elif [[ "$f" == *".test.txt" ]]; then - continue - fi - # Ensure that files are UTF-8 formatted. - isutf8 "$f" >> utf8-validation.txt 2>&1 - # Ensure that files have LF line endings and do not contain a BOM. - dos2unix "$f" 2> /dev/null - # Remove trailing space characters and ensures that files end - # with newline characters. -l option handles newlines conveniently. - perl -i -ple 's/\s*$//g' "$f" -done - -diff=$(git diff --color) - -if [ ! -s utf8-validation.txt ] && [ -z "$diff" ] ; then - # If no UTF-8 violations were collected (the file is empty) and - # no diff has been generated all is OK, clean up, and exit. - printf "\e[1;32m*** Files in this commit comply with the file formatting rules.\e[0m\n" - rm -f utf8-validation.txt - exit 0 -fi - -if [ -s utf8-validation.txt ] -then - # If the file has content and is not empty, violations - # detected, notify the user, clean up, and exit. - printf "\n\e[1;33m*** The following files contain invalid UTF-8 character sequences:\e[0m\n\n" - cat utf8-validation.txt -fi - -rm -f utf8-validation.txt - -if [ ! -z "$diff" ] -then - # A diff has been created, notify the user, clean up, and exit. - printf "\n\e[1;33m*** The following changes must be made to comply with the formatting rules:\e[0m\n\n" - # Perl commands replace trailing spaces with `·` and tabs with `<TAB>`. - printf "%s\n" "$diff" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' -fi - -printf "\n\e[1;91m*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\e[0m\n" -exit 1 diff --git a/misc/scripts/header_guards.py b/misc/scripts/header_guards.py new file mode 100644 index 0000000000..b554be5159 --- /dev/null +++ b/misc/scripts/header_guards.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +from pathlib import Path + +if len(sys.argv) < 2: + print("Invalid usage of header_guards.py, it should be called with a path to one or multiple files.") + sys.exit(1) + +HEADER_CHECK_OFFSET = 30 +HEADER_BEGIN_OFFSET = 31 +HEADER_END_OFFSET = -1 + +changed = [] +invalid = [] + +for file in sys.argv[1:]: + with open(file, "rt", encoding="utf-8", newline="\n") as f: + lines = f.readlines() + + if len(lines) <= HEADER_BEGIN_OFFSET: + continue # Most likely a dummy file. + + if lines[HEADER_CHECK_OFFSET].startswith("#import"): + continue # Early catch obj-c file. + + split = file.split("/") # Already in posix-format. + + prefix = "" + if split[0] == "modules" and split[-1] == "register_types.h": + prefix = f"{split[1]}_" # Name of module. + elif split[0] == "platform" and (file.endswith("api/api.h") or "/export/" in file): + prefix = f"{split[1]}_" # Name of platform. + elif file.startswith("modules/mono/utils") and "mono" not in split[-1]: + prefix = "MONO_" + elif file == "servers/rendering/storage/utilities.h": + prefix = "RENDERER_" + + suffix = "" + if "dummy" in file and "dummy" not in split[-1]: + suffix = "_DUMMY" + elif "gles3" in file and "gles3" not in split[-1]: + suffix = "_GLES3" + elif "renderer_rd" in file and "rd" not in split[-1]: + suffix = "_RD" + elif split[-1] == "ustring.h": + suffix = "_GODOT" + + name = (f"{prefix}{Path(file).stem}{suffix}{Path(file).suffix}".upper() + .replace(".", "_").replace("-", "_").replace(" ", "_")) # fmt: skip + + HEADER_CHECK = f"#ifndef {name}\n" + HEADER_BEGIN = f"#define {name}\n" + HEADER_END = f"#endif // {name}\n" + + if ( + lines[HEADER_CHECK_OFFSET] == HEADER_CHECK + and lines[HEADER_BEGIN_OFFSET] == HEADER_BEGIN + and lines[HEADER_END_OFFSET] == HEADER_END + ): + continue + + # Guards might exist but with the wrong names. + if ( + lines[HEADER_CHECK_OFFSET].startswith("#ifndef") + and lines[HEADER_BEGIN_OFFSET].startswith("#define") + and lines[HEADER_END_OFFSET].startswith("#endif") + ): + lines[HEADER_CHECK_OFFSET] = HEADER_CHECK + lines[HEADER_BEGIN_OFFSET] = HEADER_BEGIN + lines[HEADER_END_OFFSET] = HEADER_END + with open(file, "wt", encoding="utf-8", newline="\n") as f: + f.writelines(lines) + changed.append(file) + continue + + header_check = -1 + header_begin = -1 + header_end = -1 + pragma_once = -1 + objc = False + + for idx, line in enumerate(lines): + if not line.startswith("#"): + continue + elif line.startswith("#ifndef") and header_check == -1: + header_check = idx + elif line.startswith("#define") and header_begin == -1: + header_begin = idx + elif line.startswith("#endif") and header_end == -1: + header_end = idx + elif line.startswith("#pragma once"): + pragma_once = idx + break + elif line.startswith("#import"): + objc = True + break + + if objc: + continue + + if pragma_once != -1: + lines.pop(pragma_once) + lines.insert(HEADER_CHECK_OFFSET, HEADER_CHECK) + lines.insert(HEADER_BEGIN_OFFSET, HEADER_BEGIN) + lines.append("\n") + lines.append(HEADER_END) + with open(file, "wt", encoding="utf-8", newline="\n") as f: + f.writelines(lines) + changed.append(file) + continue + + if header_check == -1 and header_begin == -1 and header_end == -1: + # Guards simply didn't exist + lines.insert(HEADER_CHECK_OFFSET, HEADER_CHECK) + lines.insert(HEADER_BEGIN_OFFSET, HEADER_BEGIN) + lines.append("\n") + lines.append(HEADER_END) + with open(file, "wt", encoding="utf-8", newline="\n") as f: + f.writelines(lines) + changed.append(file) + continue + + if header_check != -1 and header_begin != -1 and header_end != -1: + # All prepends "found", see if we can salvage this. + if header_check == header_begin - 1 and header_begin < header_end: + lines.pop(header_check) + lines.pop(header_begin - 1) + lines.pop(header_end - 2) + if lines[header_end - 3] == "\n": + lines.pop(header_end - 3) + lines.insert(HEADER_CHECK_OFFSET, HEADER_CHECK) + lines.insert(HEADER_BEGIN_OFFSET, HEADER_BEGIN) + lines.append("\n") + lines.append(HEADER_END) + with open(file, "wt", encoding="utf-8", newline="\n") as f: + f.writelines(lines) + changed.append(file) + continue + + invalid.append(file) + +if changed: + for file in changed: + print(f"FIXED: {file}") +if invalid: + for file in invalid: + print(f"REQUIRES MANUAL CHANGES: {file}") + sys.exit(1) diff --git a/misc/scripts/header_guards.sh b/misc/scripts/header_guards.sh deleted file mode 100755 index a79ccd4bee..0000000000 --- a/misc/scripts/header_guards.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash - -if [ ! -f "version.py" ]; then - echo "Warning: This script is intended to be run from the root of the Godot repository." - echo "Some of the paths checks may not work as intended from a different folder." -fi - -if [ $# -eq 0 ]; then - # Loop through all code files tracked by Git. - files=$(find -name "thirdparty" -prune -o -name "*.h" -print | sed "s@^\./@@g") -else - # $1 should be a file listing file paths to process. Used in CI. - files=$(cat "$1" | grep -v "thirdparty/" | grep -E "\.h$" | sed "s@^\./@@g") -fi - -files_invalid_guard="" - -for file in $files; do - # Skip *.gen.h and *-so_wrap.h, they're generated. - if [[ "$file" == *".gen.h" || "$file" == *"-so_wrap.h" ]]; then continue; fi - # Has important define before normal header guards. - if [[ "$file" == *"thread.h" || "$file" == *"platform_config.h" || "$file" == *"platform_gl.h" ]]; then continue; fi - # Obj-C files don't use header guards. - if grep -q "#import " "$file"; then continue; fi - - bname=$(basename $file .h) - - # Add custom prefix or suffix for generic filenames with a well-defined namespace. - - prefix= - if [[ "$file" == "modules/"*"/register_types.h" ]]; then - module=$(echo $file | sed "s@.*modules/\([^/]*\).*@\1@") - prefix="${module^^}_" - fi - if [[ "$file" == "platform/"*"/api/api.h" || "$file" == "platform/"*"/export/"* ]]; then - platform=$(echo $file | sed "s@.*platform/\([^/]*\).*@\1@") - prefix="${platform^^}_" - fi - if [[ "$file" == "modules/mono/utils/"* && "$bname" != *"mono"* ]]; then prefix="MONO_"; fi - if [[ "$file" == "servers/rendering/storage/utilities.h" ]]; then prefix="RENDERER_"; fi - - suffix= - if [[ "$file" == *"dummy"* && "$bname" != *"dummy"* ]]; then suffix="_DUMMY"; fi - if [[ "$file" == *"gles3"* && "$bname" != *"gles3"* ]]; then suffix="_GLES3"; fi - if [[ "$file" == *"renderer_rd"* && "$bname" != *"rd"* ]]; then suffix="_RD"; fi - if [[ "$file" == *"ustring.h" ]]; then suffix="_GODOT"; fi - - # ^^ is bash builtin for UPPERCASE. - guard="${prefix}${bname^^}${suffix}_H" - - # Replaces guards to use computed name. - # We also add some \n to make sure there's a proper separation. - sed -i $file -e "0,/ifndef/s/#ifndef.*/\n#ifndef $guard/" - sed -i $file -e "0,/define/s/#define.*/#define $guard\n/" - sed -i $file -e "$ s/#endif.*/\n#endif \/\/ $guard/" - # Removes redundant \n added before, if they weren't needed. - sed -i $file -e "/^$/N;/^\n$/D" - - # Check that first ifndef (should be header guard) is at the expected position. - # If not it can mean we have some code before the guard that should be after. - # "31" is the expected line with the copyright header. - first_ifndef=$(grep -n -m 1 "ifndef" $file | sed 's/\([0-9]*\).*/\1/') - if [[ "$first_ifndef" != "31" ]]; then - files_invalid_guard+="$file\n" - fi -done - -if [[ ! -z "$files_invalid_guard" ]]; then - echo -e "The following files were found to have potentially invalid header guard:\n" - echo -e "$files_invalid_guard" -fi - -diff=$(git diff --color) - -# If no diff has been generated all is OK, clean up, and exit. -if [ -z "$diff" ] ; then - printf "\e[1;32m*** Files in this commit comply with the header guards formatting rules.\e[0m\n" - exit 0 -fi - -# A diff has been created, notify the user, clean up, and exit. -printf "\n\e[1;33m*** The following changes must be made to comply with the formatting rules:\e[0m\n\n" -# Perl commands replace trailing spaces with `·` and tabs with `<TAB>`. -printf "%s\n" "$diff" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' - -printf "\n\e[1;91m*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\e[0m\n" -exit 1 diff --git a/misc/scripts/mypy_check.sh b/misc/scripts/mypy_check.sh deleted file mode 100755 index 2a06486d67..0000000000 --- a/misc/scripts/mypy_check.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -uo pipefail - -echo -e "Python: mypy static analysis..." -mypy --config-file=./misc/scripts/mypy.ini . diff --git a/misc/scripts/pytest_builders.sh b/misc/scripts/pytest_builders.sh deleted file mode 100755 index eb2ddbcddc..0000000000 --- a/misc/scripts/pytest_builders.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -set -uo pipefail - -echo "Running Python checks for builder system" -pytest ./tests/python_build diff --git a/modules/SCsub b/modules/SCsub index 6fb2aa67f4..739c5de0b5 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -1,6 +1,6 @@ #!/usr/bin/env python -import modules_builders +import methods import os Import("env") @@ -12,15 +12,51 @@ env_modules.Append(CPPDEFINES=["GODOT_MODULE"]) Export("env_modules") -# Header with MODULE_*_ENABLED defines. -env.Depends("modules_enabled.gen.h", Value(env.module_list)) -env.CommandNoCache( - "modules_enabled.gen.h", - Value(env.module_list), - env.Run(modules_builders.generate_modules_enabled), + +def register_module_types_builder(target, source, env): + modules = source[0].read() + mod_inc = "\n".join([f'#include "{p}/register_types.h"' for p in modules.values()]) + mod_init = "\n".join( + [f"#ifdef MODULE_{n.upper()}_ENABLED\n\tinitialize_{n}_module(p_level);\n#endif" for n in modules.keys()] + ) + mod_uninit = "\n".join( + [f"#ifdef MODULE_{n.upper()}_ENABLED\n\tuninitialize_{n}_module(p_level);\n#endif" for n in modules.keys()] + ) + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +#include "register_module_types.h" + +#include "modules/modules_enabled.gen.h" + +{mod_inc} + +void initialize_modules(ModuleInitializationLevel p_level) {{ +{mod_init} +}} + +void uninitialize_modules(ModuleInitializationLevel p_level) {{ +{mod_uninit} +}} +""" + ) + + +register_module_types = env.CommandNoCache( + "register_module_types.gen.cpp", env.Value(env.modules_detected), env.Run(register_module_types_builder) ) +# Header with MODULE_*_ENABLED defines. +def modules_enabled_builder(target, source, env): + with methods.generated_wrapper(target) as file: + for module in source[0].read(): + file.write(f"#define MODULE_{module.upper()}_ENABLED\n") + + +env.CommandNoCache("modules_enabled.gen.h", env.Value(env.module_list), env.Run(modules_enabled_builder)) + + vs_sources = [] test_headers = [] # libmodule_<name>.a for each active module. @@ -47,18 +83,19 @@ for name, path in env.module_list.items(): # Generate header to be included in `tests/test_main.cpp` to run module-specific tests. if env["tests"]: - env.Depends("modules_tests.gen.h", test_headers) - env.CommandNoCache( - "modules_tests.gen.h", - test_headers, - env.Run(modules_builders.generate_modules_tests), - ) + + def modules_tests_builder(target, source, env): + with methods.generated_wrapper(target) as file: + for header in source: + file.write('#include "{}"\n'.format(os.path.normpath(header.path).replace("\\", "/"))) + + env.CommandNoCache("modules_tests.gen.h", test_headers, env.Run(modules_tests_builder)) # libmodules.a with only register_module_types. # Must be last so that all libmodule_<name>.a libraries are on the right side # in the linker command. env.modules_sources = [] -env_modules.add_source_files(env.modules_sources, "register_module_types.gen.cpp") +env_modules.add_source_files(env.modules_sources, register_module_types) lib = env_modules.add_library("modules", env.modules_sources) env.Prepend(LIBS=[lib]) if env["vsproj"]: diff --git a/modules/modules_builders.py b/modules/modules_builders.py deleted file mode 100644 index 5db7c88a90..0000000000 --- a/modules/modules_builders.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Functions used to generate source files during build time""" - - -def generate_modules_enabled(target, source, env): - with open(target[0].path, "w", encoding="utf-8", newline="\n") as f: - for module in env.module_list: - f.write("#define %s\n" % ("MODULE_" + module.upper() + "_ENABLED")) - - -def generate_modules_tests(target, source, env): - import os - - with open(target[0].path, "w", encoding="utf-8", newline="\n") as f: - for header in source: - f.write('#include "%s"\n' % (os.path.normpath(header.path))) diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index ff2ca9f0ce..36c8a40ed9 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1596,14 +1596,7 @@ void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { return; } - const CSharpScript *top = script.ptr(); - while (top != nullptr) { - for (const CSharpScript::CSharpMethodInfo &E : top->methods) { - p_list->push_back(E.method_info); - } - - top = top->base_script.ptr(); - } + script->get_script_method_list(p_list); } bool CSharpInstance::has_method(const StringName &p_method) const { @@ -1771,16 +1764,19 @@ void CSharpInstance::mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_f } void CSharpInstance::connect_event_signals() { - // The script signals list includes the signals declared in base scripts. - for (CSharpScript::EventSignalInfo &signal : script->get_script_event_signals()) { - String signal_name = signal.name; + const CSharpScript *top = script.ptr(); + while (top != nullptr && top->valid) { + for (const CSharpScript::EventSignalInfo &signal : top->event_signals) { + String signal_name = signal.name; - // TODO: Use pooling for ManagedCallable instances. - EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, signal_name)); + // TODO: Use pooling for ManagedCallable instances. + EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, signal_name)); - Callable callable(event_signal_callable); - connected_event_signals.push_back(callable); - owner->connect(signal_name, callable); + Callable callable(event_signal_callable); + connected_event_signals.push_back(callable); + owner->connect(signal_name, callable); + } + top = top->base_script.ptr(); } } @@ -2624,25 +2620,33 @@ bool CSharpScript::has_script_signal(const StringName &p_signal) const { } } + if (base_script.is_valid()) { + return base_script->has_script_signal(p_signal); + } + return false; } -void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { +void CSharpScript::_get_script_signal_list(List<MethodInfo> *r_signals, bool p_include_base) const { if (!valid) { return; } - for (const EventSignalInfo &signal : get_script_event_signals()) { + for (const EventSignalInfo &signal : event_signals) { r_signals->push_back(signal.method_info); } -} -Vector<CSharpScript::EventSignalInfo> CSharpScript::get_script_event_signals() const { - if (!valid) { - return Vector<EventSignalInfo>(); + if (!p_include_base) { + return; + } + + if (base_script.is_valid()) { + base_script->get_script_signal_list(r_signals); } +} - return event_signals; +void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { + _get_script_signal_list(r_signals, true); } bool CSharpScript::inherits_script(const Ref<Script> &p_script) const { diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 17df3988ee..c48e1a95c9 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -215,6 +215,8 @@ private: // Do not use unless you know what you are doing static void update_script_class_info(Ref<CSharpScript> p_script); + void _get_script_signal_list(List<MethodInfo> *r_signals, bool p_include_base) const; + protected: static void _bind_methods(); @@ -251,8 +253,6 @@ public: bool has_script_signal(const StringName &p_signal) const override; void get_script_signal_list(List<MethodInfo> *r_signals) const override; - Vector<EventSignalInfo> get_script_event_signals() const; - bool get_property_default_value(const StringName &p_property, Variant &r_value) const override; void get_script_property_list(List<PropertyInfo> *r_list) const override; void update_exports() override; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index 1b23276bbd..eef26cdd4e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -798,17 +798,17 @@ namespace Godot.Bridge GetScriptTypeInfo(scriptType, outTypeInfo); + Type native = GodotObject.InternalGetClassNativeBase(scriptType); + // Methods // Performance is not critical here as this will be replaced with source generators. using var methods = new Collections.Array(); Type? top = scriptType; - Type native = GodotObject.InternalGetClassNativeBase(top); - - while (top != null && top != native) + if (scriptType != native) { - var methodList = GetMethodListForType(top); + var methodList = GetMethodListForType(scriptType); if (methodList != null) { @@ -859,8 +859,6 @@ namespace Godot.Bridge methods.Add(methodInfo); } } - - top = top.BaseType; } *outMethodsDest = NativeFuncs.godotsharp_array_new_copy( @@ -912,11 +910,9 @@ namespace Godot.Bridge // Performance is not critical here as this will be replaced with source generators. using var signals = new Collections.Dictionary(); - top = scriptType; - - while (top != null && top != native) + if (scriptType != native) { - var signalList = GetSignalListForType(top); + var signalList = GetSignalListForType(scriptType); if (signalList != null) { @@ -951,8 +947,6 @@ namespace Godot.Bridge signals.Add(signalName, signalParams); } } - - top = top.BaseType; } *outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new_copy( diff --git a/platform/SCsub b/platform/SCsub index ca282e3e68..b24c189848 100644 --- a/platform/SCsub +++ b/platform/SCsub @@ -1,27 +1,64 @@ #!/usr/bin/env python import methods +from glob import glob +from pathlib import Path Import("env") env.platform_sources = [] + +# Generate export icons +def export_icon_builder(target, source, env): + src_path = Path(str(source[0])) + src_name = src_path.stem + platform = src_path.parent.parent.stem + with open(str(source[0]), "rb") as file: + svg = "".join([f"\\{hex(x)[1:]}" for x in file.read()]) + with methods.generated_wrapper(target, prefix=platform) as file: + file.write( + f"""\ +static const char *_{platform}_{src_name}_svg = "{svg}"; +""" + ) + + +for platform in env.platform_exporters: + for path in glob(f"{platform}/export/*.svg"): + env.CommandNoCache(path.replace(".svg", "_svg.gen.h"), path, env.Run(export_icon_builder)) + + # Register platform-exclusive APIs -reg_apis_inc = '#include "register_platform_apis.h"\n' -reg_apis = "void register_platform_apis() {\n" -unreg_apis = "void unregister_platform_apis() {\n" +def register_platform_apis_builder(target, source, env): + platforms = source[0].read() + api_inc = "\n".join([f'#include "{p}/api/api.h"' for p in platforms]) + api_reg = "\n".join([f"\tregister_{p}_api();" for p in platforms]) + api_unreg = "\n".join([f"\tunregister_{p}_api();" for p in platforms]) + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +#include "register_platform_apis.h" + +{api_inc} + +void register_platform_apis() {{ +{api_reg} +}} + +void unregister_platform_apis() {{ +{api_unreg} +}} +""" + ) + + +register_platform_apis = env.CommandNoCache( + "register_platform_apis.gen.cpp", env.Value(env.platform_apis), env.Run(register_platform_apis_builder) +) +env.add_source_files(env.platform_sources, register_platform_apis) for platform in env.platform_apis: - platform_dir = env.Dir(platform) - env.add_source_files(env.platform_sources, platform + "/api/api.cpp") - reg_apis += "\tregister_" + platform + "_api();\n" - unreg_apis += "\tunregister_" + platform + "_api();\n" - reg_apis_inc += '#include "' + platform + '/api/api.h"\n' -reg_apis_inc += "\n" -reg_apis += "}\n\n" -unreg_apis += "}\n" - -methods.write_file_if_needed("register_platform_apis.gen.cpp", reg_apis_inc + reg_apis + unreg_apis) -env.add_source_files(env.platform_sources, "register_platform_apis.gen.cpp") + env.add_source_files(env.platform_sources, f"{platform}/api/api.cpp") lib = env.add_library("platform", env.platform_sources) env.Prepend(LIBS=[lib]) diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index b76cbc126f..9174b65b1b 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -4268,6 +4268,8 @@ bool DisplayServerX11::_window_focus_check() { } void DisplayServerX11::process_events() { + ERR_FAIL_COND(!Thread::is_main_thread()); + _THREAD_SAFE_LOCK_ #ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 0041848c78..e093f01a8a 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2985,7 +2985,7 @@ Key DisplayServerMacOS::keyboard_get_label_from_physical(Key p_keycode) const { } void DisplayServerMacOS::process_events() { - _THREAD_SAFE_LOCK_ + ERR_FAIL_COND(!Thread::is_main_thread()); while (true) { NSEvent *event = [NSApp @@ -3018,11 +3018,11 @@ void DisplayServerMacOS::process_events() { if (!drop_events) { _process_key_events(); - _THREAD_SAFE_UNLOCK_ Input::get_singleton()->flush_buffered_events(); - _THREAD_SAFE_LOCK_ } + _THREAD_SAFE_LOCK_ + for (KeyValue<WindowID, WindowData> &E : windows) { WindowData &wd = E.value; if (wd.mpass) { @@ -3051,7 +3051,7 @@ void DisplayServerMacOS::process_events() { } void DisplayServerMacOS::force_process_and_drop_events() { - _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(!Thread::is_main_thread()); drop_events = true; process_events(); diff --git a/platform/windows/detect.py b/platform/windows/detect.py index ccf889f1a3..93eb34001e 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -18,30 +18,36 @@ def get_name(): return "Windows" -def get_mingw_tool(tool, prefix="", arch="", test="--version"): - if not prefix: - prefix = os.getenv("MINGW_PREFIX", "") - supported_arches = ["x86_64", "x86_32", "arm64", "arm32"] - if arch in supported_arches: - arches = [arch, ""] - else: - arches = ["x86_64", "x86_32", "arm64", "arm32", ""] - for a in arches: +def try_cmd(test, prefix, arch): + if arch: try: - path = f"{get_mingw_bin_prefix(prefix, a)}{tool}" out = subprocess.Popen( - f"{path} {test}", + get_mingw_bin_prefix(prefix, arch) + test, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, ) out.communicate() if out.returncode == 0: - return path + return True except Exception: pass + else: + for a in ["x86_64", "x86_32", "arm64", "arm32"]: + try: + out = subprocess.Popen( + get_mingw_bin_prefix(prefix, a) + test, + shell=True, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + out.communicate() + if out.returncode == 0: + return True + except Exception: + pass - return "" + return False def can_build(): @@ -59,7 +65,9 @@ def can_build(): if os.name == "posix": # Cross-compiling with MinGW-w64 (old MinGW32 is not supported) - if get_mingw_tool("gcc") or get_mingw_tool("clang"): + prefix = os.getenv("MINGW_PREFIX", "") + + if try_cmd("gcc --version", prefix, "") or try_cmd("clang --version", prefix, ""): return True return False @@ -247,26 +255,36 @@ def get_flags(): def build_res_file(target, source, env: "SConsEnvironment"): - cmdbase = get_mingw_tool("windres", env["mingw_prefix"], env["arch"]) - if not cmdbase: - return -1 - arch_aliases = { "x86_32": "pe-i386", "x86_64": "pe-x86-64", "arm32": "armv7-w64-mingw32", "arm64": "aarch64-w64-mingw32", } - cmdbase += " --include-dir . --target=" + arch_aliases[env["arch"]] + cmdbase = "windres --include-dir . --target=" + arch_aliases[env["arch"]] + + mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) for x in range(len(source)): - cmd = f"{cmdbase} -i {source[x]} -o {target[x]}" + ok = True + # Try prefixed executable (MinGW on Linux). + cmd = mingw_bin_prefix + cmdbase + " -i " + str(source[x]) + " -o " + str(target[x]) try: out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() if len(out[1]): - return -1 + ok = False except Exception: - return -1 + ok = False + + # Try generic executable (MSYS2). + if not ok: + cmd = cmdbase + " -i " + str(source[x]) + " -o " + str(target[x]) + try: + out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() + if len(out[1]): + return -1 + except Exception: + return -1 return 0 @@ -340,8 +358,8 @@ def setup_mingw(env: "SConsEnvironment"): ) sys.exit(255) - if not get_mingw_tool("gcc", env["mingw_prefix"], env["arch"]) and not get_mingw_tool( - "clang", env["mingw_prefix"], env["arch"] + if not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]) and not try_cmd( + "clang --version", env["mingw_prefix"], env["arch"] ): print_error("No valid compilers found, use MINGW_PREFIX environment variable to set MinGW path.") sys.exit(255) @@ -582,10 +600,10 @@ def configure_mingw(env: "SConsEnvironment"): ## Build type - if not env["use_llvm"] and not get_mingw_tool("gcc", env["mingw_prefix"], env["arch"]): + if not env["use_llvm"] and not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]): env["use_llvm"] = True - if env["use_llvm"] and not get_mingw_tool("clang", env["mingw_prefix"], env["arch"]): + if env["use_llvm"] and not try_cmd("clang --version", env["mingw_prefix"], env["arch"]): env["use_llvm"] = False # TODO: Re-evaluate the need for this / streamline with common config. @@ -620,26 +638,27 @@ def configure_mingw(env: "SConsEnvironment"): if env["arch"] in ["x86_32", "x86_64"]: env["x86_libtheora_opt_gcc"] = True + mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) + if env["use_llvm"]: - env["CC"] = get_mingw_tool("clang", env["mingw_prefix"], env["arch"]) - env["CXX"] = get_mingw_tool("clang++", env["mingw_prefix"], env["arch"]) - tool_as = get_mingw_tool("as", env["mingw_prefix"], env["arch"]) - tool_ar = get_mingw_tool("ar", env["mingw_prefix"], env["arch"]) - tool_ranlib = get_mingw_tool("ranlib", env["mingw_prefix"], env["arch"]) + env["CC"] = mingw_bin_prefix + "clang" + env["CXX"] = mingw_bin_prefix + "clang++" + if try_cmd("as --version", env["mingw_prefix"], env["arch"]): + env["AS"] = mingw_bin_prefix + "as" + if try_cmd("ar --version", env["mingw_prefix"], env["arch"]): + env["AR"] = mingw_bin_prefix + "ar" + if try_cmd("ranlib --version", env["mingw_prefix"], env["arch"]): + env["RANLIB"] = mingw_bin_prefix + "ranlib" env.extra_suffix = ".llvm" + env.extra_suffix else: - env["CC"] = get_mingw_tool("gcc", env["mingw_prefix"], env["arch"]) - env["CXX"] = get_mingw_tool("g++", env["mingw_prefix"], env["arch"]) - tool_as = get_mingw_tool("as", env["mingw_prefix"], env["arch"]) - tool_ar = get_mingw_tool("gcc-ar", env["mingw_prefix"], env["arch"]) - tool_ranlib = get_mingw_tool("gcc-ranlib", env["mingw_prefix"], env["arch"]) - - if tool_as: - env["AS"] = tool_as - if tool_ar: - env["AR"] = tool_ar - if tool_ranlib: - env["RANLIB"] = tool_ranlib + env["CC"] = mingw_bin_prefix + "gcc" + env["CXX"] = mingw_bin_prefix + "g++" + if try_cmd("as --version", env["mingw_prefix"], env["arch"]): + env["AS"] = mingw_bin_prefix + "as" + if try_cmd("gcc-ar --version", env["mingw_prefix"], env["arch"]): + env["AR"] = mingw_bin_prefix + "gcc-ar" + if try_cmd("gcc-ranlib --version", env["mingw_prefix"], env["arch"]): + env["RANLIB"] = mingw_bin_prefix + "gcc-ranlib" ## LTO diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 94c368c504..e79d16629e 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -2962,30 +2962,28 @@ String DisplayServerWindows::keyboard_get_layout_name(int p_index) const { } void DisplayServerWindows::process_events() { - _THREAD_SAFE_LOCK_ - - MSG msg; + ERR_FAIL_COND(!Thread::is_main_thread()); if (!drop_events) { joypad->process_joypads(); } + _THREAD_SAFE_LOCK_ + MSG msg = {}; while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessageW(&msg); } + _THREAD_SAFE_UNLOCK_ if (!drop_events) { _process_key_events(); - _THREAD_SAFE_UNLOCK_ Input::get_singleton()->flush_buffered_events(); - } else { - _THREAD_SAFE_UNLOCK_ } } void DisplayServerWindows::force_process_and_drop_events() { - _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(!Thread::is_main_thread()); drop_events = true; process_events(); @@ -4664,10 +4662,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_TIMER: { if (wParam == windows[window_id].move_timer_id) { + _THREAD_SAFE_UNLOCK_ _process_key_events(); if (!Main::is_iterating()) { Main::iteration(); } + _THREAD_SAFE_LOCK_ } else if (wParam == windows[window_id].activate_timer_id) { _process_activate_event(window_id); KillTimer(windows[window_id].hWnd, windows[window_id].activate_timer_id); diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py index dd480571ba..729d55cea6 100644 --- a/platform/windows/platform_windows_builders.py +++ b/platform/windows/platform_windows_builders.py @@ -1,20 +1,24 @@ """Functions used to generate source files during build time""" import os -from detect import get_mingw_tool +from detect import get_mingw_bin_prefix +from detect import try_cmd def make_debug_mingw(target, source, env): dst = str(target[0]) # Force separate debug symbols if executable size is larger than 1.9 GB. if env["separate_debug_symbols"] or os.stat(dst).st_size >= 2040109465: - objcopy = get_mingw_tool("objcopy", env["mingw_prefix"], env["arch"]) - strip = get_mingw_tool("strip", env["mingw_prefix"], env["arch"]) - - if not objcopy or not strip: - print('`separate_debug_symbols` requires both "objcopy" and "strip" to function.') - return - - os.system("{0} --only-keep-debug {1} {1}.debugsymbols".format(objcopy, dst)) - os.system("{0} --strip-debug --strip-unneeded {1}".format(strip, dst)) - os.system("{0} --add-gnu-debuglink={1}.debugsymbols {1}".format(objcopy, dst)) + mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) + if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst)) + else: + os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst)) + if try_cmd("strip --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(dst)) + else: + os.system("strip --strip-debug --strip-unneeded {0}".format(dst)) + if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst)) + else: + os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst)) diff --git a/platform_methods.py b/platform_methods.py index 57b11d1a47..5326e36077 100644 --- a/platform_methods.py +++ b/platform_methods.py @@ -43,33 +43,6 @@ def detect_arch(): return "x86_64" -def generate_export_icons(platform_path, platform_name): - """ - Generate headers for logo and run icon for the export plugin. - """ - export_path = platform_path + "/export" - svg_names = [] - if os.path.isfile(export_path + "/logo.svg"): - svg_names.append("logo") - if os.path.isfile(export_path + "/run_icon.svg"): - svg_names.append("run_icon") - - for name in svg_names: - with open(export_path + "/" + name + ".svg", "rb") as svgf: - b = svgf.read(1) - svg_str = " /* AUTOGENERATED FILE, DO NOT EDIT */ \n" - svg_str += " static const char *_" + platform_name + "_" + name + '_svg = "' - while len(b) == 1: - svg_str += "\\" + hex(ord(b))[1:] - b = svgf.read(1) - - svg_str += '";\n' - - wf = export_path + "/" + name + "_svg.gen.h" - - methods.write_file_if_needed(wf, svg_str) - - def get_build_version(short): import version diff --git a/scene/3d/skeleton_modifier_3d.cpp b/scene/3d/skeleton_modifier_3d.cpp index 96e3e33841..8d806ef5fc 100644 --- a/scene/3d/skeleton_modifier_3d.cpp +++ b/scene/3d/skeleton_modifier_3d.cpp @@ -110,7 +110,7 @@ void SkeletonModifier3D::process_modification() { } void SkeletonModifier3D::_process_modification() { - // + GDVIRTUAL_CALL(_process_modification); } void SkeletonModifier3D::_notification(int p_what) { @@ -133,6 +133,7 @@ void SkeletonModifier3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "influence", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_influence", "get_influence"); ADD_SIGNAL(MethodInfo("modification_processed")); + GDVIRTUAL_BIND(_process_modification); } SkeletonModifier3D::SkeletonModifier3D() { diff --git a/scene/3d/skeleton_modifier_3d.h b/scene/3d/skeleton_modifier_3d.h index 25c09f3b93..d00a1e94a9 100644 --- a/scene/3d/skeleton_modifier_3d.h +++ b/scene/3d/skeleton_modifier_3d.h @@ -60,6 +60,7 @@ protected: virtual void _set_active(bool p_active); virtual void _process_modification(); + GDVIRTUAL0(_process_modification); public: virtual PackedStringArray get_configuration_warnings() const override; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 6fac096c93..de94327d79 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -589,7 +589,7 @@ void register_scene_types() { GDREGISTER_CLASS(CPUParticles3D); GDREGISTER_CLASS(Marker3D); GDREGISTER_CLASS(RootMotionView); - GDREGISTER_ABSTRACT_CLASS(SkeletonModifier3D); + GDREGISTER_VIRTUAL_CLASS(SkeletonModifier3D); OS::get_singleton()->yield(); // may take time to init diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp index ba5dad088f..db2564af22 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -542,7 +542,7 @@ double AudioStreamWAV::get_length() const { break; case AudioStreamWAV::FORMAT_QOA: qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } }; - qoa_decode_header((uint8_t *)data + DATA_PAD, QOA_MIN_FILESIZE, &desc); + qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc); len = desc.samples * desc.channels; } @@ -681,7 +681,8 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() { if (format == AudioStreamWAV::FORMAT_QOA) { sample->qoa.desc = (qoa_desc *)memalloc(sizeof(qoa_desc)); - qoa_decode_header((uint8_t *)data + DATA_PAD, QOA_MIN_FILESIZE, sample->qoa.desc); + uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, sample->qoa.desc); + ERR_FAIL_COND_V(ffp != 8, Ref<AudioStreamPlaybackWAV>()); sample->qoa.frame_len = qoa_max_frame_size(sample->qoa.desc); int samples_len = (sample->qoa.desc->samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc->samples); int alloc_len = sample->qoa.desc->channels * samples_len * sizeof(int16_t); diff --git a/servers/physics_server_2d_wrap_mt.cpp b/servers/physics_server_2d_wrap_mt.cpp index 4548bb91cb..8e9f7aa8fc 100644 --- a/servers/physics_server_2d_wrap_mt.cpp +++ b/servers/physics_server_2d_wrap_mt.cpp @@ -32,45 +32,37 @@ #include "core/os/os.h" -void PhysicsServer2DWrapMT::thread_exit() { - exit = true; +void PhysicsServer2DWrapMT::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) { + server_thread = Thread::get_caller_id(); + server_task_id = p_pump_task_id; } -void PhysicsServer2DWrapMT::thread_step(real_t p_delta) { - physics_server_2d->step(p_delta); - step_sem.post(); +void PhysicsServer2DWrapMT::_thread_exit() { + exit = true; } -void PhysicsServer2DWrapMT::thread_loop() { - server_thread = Thread::get_caller_id(); - - physics_server_2d->init(); - - command_queue.set_pump_task_id(server_task_id); +void PhysicsServer2DWrapMT::_thread_loop() { while (!exit) { WorkerThreadPool::get_singleton()->yield(); command_queue.flush_all(); } - - command_queue.flush_all(); - - physics_server_2d->finish(); } /* EVENT QUEUING */ void PhysicsServer2DWrapMT::step(real_t p_step) { if (create_thread) { - command_queue.push(this, &PhysicsServer2DWrapMT::thread_step, p_step); + command_queue.push(physics_server_2d, &PhysicsServer2D::step, p_step); } else { - command_queue.flush_all(); // Flush all pending from other threads. physics_server_2d->step(p_step); } } void PhysicsServer2DWrapMT::sync() { if (create_thread) { - step_sem.wait(); + command_queue.sync(); + } else { + command_queue.flush_all(); // Flush all pending from other threads. } physics_server_2d->sync(); } @@ -85,21 +77,26 @@ void PhysicsServer2DWrapMT::end_sync() { void PhysicsServer2DWrapMT::init() { if (create_thread) { - exit = false; - server_task_id = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &PhysicsServer2DWrapMT::thread_loop), true); - step_sem.post(); + WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &PhysicsServer2DWrapMT::_thread_loop), true); + command_queue.set_pump_task_id(tid); + command_queue.push(this, &PhysicsServer2DWrapMT::_assign_mt_ids, tid); + command_queue.push_and_sync(physics_server_2d, &PhysicsServer2D::init); + DEV_ASSERT(server_task_id == tid); } else { + server_thread = Thread::MAIN_ID; physics_server_2d->init(); } } void PhysicsServer2DWrapMT::finish() { if (create_thread) { - command_queue.push(this, &PhysicsServer2DWrapMT::thread_exit); + command_queue.push(physics_server_2d, &PhysicsServer2D::finish); + command_queue.push(this, &PhysicsServer2DWrapMT::_thread_exit); if (server_task_id != WorkerThreadPool::INVALID_TASK_ID) { WorkerThreadPool::get_singleton()->wait_for_task_completion(server_task_id); server_task_id = WorkerThreadPool::INVALID_TASK_ID; } + server_thread = Thread::MAIN_ID; } else { physics_server_2d->finish(); } @@ -108,9 +105,6 @@ void PhysicsServer2DWrapMT::finish() { PhysicsServer2DWrapMT::PhysicsServer2DWrapMT(PhysicsServer2D *p_contained, bool p_create_thread) { physics_server_2d = p_contained; create_thread = p_create_thread; - if (!create_thread) { - server_thread = Thread::MAIN_ID; - } } PhysicsServer2DWrapMT::~PhysicsServer2DWrapMT() { diff --git a/servers/physics_server_2d_wrap_mt.h b/servers/physics_server_2d_wrap_mt.h index 5e2b3b4086..f0c36a906f 100644 --- a/servers/physics_server_2d_wrap_mt.h +++ b/servers/physics_server_2d_wrap_mt.h @@ -53,17 +53,14 @@ class PhysicsServer2DWrapMT : public PhysicsServer2D { mutable CommandQueueMT command_queue; - void thread_loop(); - Thread::ID server_thread = Thread::UNASSIGNED_ID; WorkerThreadPool::TaskID server_task_id = WorkerThreadPool::INVALID_TASK_ID; bool exit = false; - Semaphore step_sem; bool create_thread = false; - void thread_step(real_t p_delta); - - void thread_exit(); + void _assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id); + void _thread_exit(); + void _thread_loop(); public: #define ServerName PhysicsServer2D diff --git a/servers/physics_server_3d_wrap_mt.cpp b/servers/physics_server_3d_wrap_mt.cpp index f8f60281a7..95b71217c4 100644 --- a/servers/physics_server_3d_wrap_mt.cpp +++ b/servers/physics_server_3d_wrap_mt.cpp @@ -32,45 +32,37 @@ #include "core/os/os.h" -void PhysicsServer3DWrapMT::thread_exit() { - exit = true; +void PhysicsServer3DWrapMT::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) { + server_thread = Thread::get_caller_id(); + server_task_id = p_pump_task_id; } -void PhysicsServer3DWrapMT::thread_step(real_t p_delta) { - physics_server_3d->step(p_delta); - step_sem.post(); +void PhysicsServer3DWrapMT::_thread_exit() { + exit = true; } -void PhysicsServer3DWrapMT::thread_loop() { - server_thread = Thread::get_caller_id(); - - physics_server_3d->init(); - - command_queue.set_pump_task_id(server_task_id); +void PhysicsServer3DWrapMT::_thread_loop() { while (!exit) { WorkerThreadPool::get_singleton()->yield(); command_queue.flush_all(); } - - command_queue.flush_all(); // flush all - - physics_server_3d->finish(); } /* EVENT QUEUING */ void PhysicsServer3DWrapMT::step(real_t p_step) { if (create_thread) { - command_queue.push(this, &PhysicsServer3DWrapMT::thread_step, p_step); + command_queue.push(physics_server_3d, &PhysicsServer3D::step, p_step); } else { - command_queue.flush_all(); // Flush all pending from other threads. physics_server_3d->step(p_step); } } void PhysicsServer3DWrapMT::sync() { if (create_thread) { - step_sem.wait(); + command_queue.sync(); + } else { + command_queue.flush_all(); // Flush all pending from other threads. } physics_server_3d->sync(); } @@ -85,21 +77,26 @@ void PhysicsServer3DWrapMT::end_sync() { void PhysicsServer3DWrapMT::init() { if (create_thread) { - exit = false; - server_task_id = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &PhysicsServer3DWrapMT::thread_loop), true); - step_sem.post(); + WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &PhysicsServer3DWrapMT::_thread_loop), true); + command_queue.set_pump_task_id(tid); + command_queue.push(this, &PhysicsServer3DWrapMT::_assign_mt_ids, tid); + command_queue.push_and_sync(physics_server_3d, &PhysicsServer3D::init); + DEV_ASSERT(server_task_id == tid); } else { + server_thread = Thread::MAIN_ID; physics_server_3d->init(); } } void PhysicsServer3DWrapMT::finish() { if (create_thread) { - command_queue.push(this, &PhysicsServer3DWrapMT::thread_exit); + command_queue.push(physics_server_3d, &PhysicsServer3D::finish); + command_queue.push(this, &PhysicsServer3DWrapMT::_thread_exit); if (server_task_id != WorkerThreadPool::INVALID_TASK_ID) { WorkerThreadPool::get_singleton()->wait_for_task_completion(server_task_id); server_task_id = WorkerThreadPool::INVALID_TASK_ID; } + server_thread = Thread::MAIN_ID; } else { physics_server_3d->finish(); } @@ -108,9 +105,6 @@ void PhysicsServer3DWrapMT::finish() { PhysicsServer3DWrapMT::PhysicsServer3DWrapMT(PhysicsServer3D *p_contained, bool p_create_thread) { physics_server_3d = p_contained; create_thread = p_create_thread; - if (!create_thread) { - server_thread = Thread::MAIN_ID; - } } PhysicsServer3DWrapMT::~PhysicsServer3DWrapMT() { diff --git a/servers/physics_server_3d_wrap_mt.h b/servers/physics_server_3d_wrap_mt.h index 22f3ee0e45..0909c46b55 100644 --- a/servers/physics_server_3d_wrap_mt.h +++ b/servers/physics_server_3d_wrap_mt.h @@ -52,17 +52,15 @@ class PhysicsServer3DWrapMT : public PhysicsServer3D { mutable CommandQueueMT command_queue; - void thread_loop(); - Thread::ID server_thread = Thread::UNASSIGNED_ID; WorkerThreadPool::TaskID server_task_id = WorkerThreadPool::INVALID_TASK_ID; bool exit = false; - Semaphore step_sem; bool create_thread = false; - void thread_step(real_t p_delta); - - void thread_exit(); + void _assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id); + void _thread_exit(); + void _thread_step(real_t p_delta); + void _thread_loop(); public: #define ServerName PhysicsServer3D diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index e739ffa535..884f8adb8c 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -678,7 +678,7 @@ void RendererViewport::draw_viewports(bool p_swap_buffers) { #endif // _3D_DISABLED if (Engine::get_singleton()->is_editor_hint()) { - set_default_clear_color(GLOBAL_GET("rendering/environment/defaults/default_clear_color")); + RSG::texture_storage->set_default_clear_color(GLOBAL_GET("rendering/environment/defaults/default_clear_color")); } if (sorted_active_viewports_dirty) { @@ -1521,10 +1521,6 @@ void RendererViewport::handle_timestamp(String p_timestamp, uint64_t p_cpu_time, } } -void RendererViewport::set_default_clear_color(const Color &p_color) { - RSG::texture_storage->set_default_clear_color(p_color); -} - void RendererViewport::viewport_set_canvas_cull_mask(RID p_viewport, uint32_t p_canvas_cull_mask) { Viewport *viewport = viewport_owner.get_or_null(p_viewport); ERR_FAIL_NULL(viewport); diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h index 8bdce04c50..b36fc7f57f 100644 --- a/servers/rendering/renderer_viewport.h +++ b/servers/rendering/renderer_viewport.h @@ -303,7 +303,6 @@ public: void handle_timestamp(String p_timestamp, uint64_t p_cpu_time, uint64_t p_gpu_time); - void set_default_clear_color(const Color &p_color); void draw_viewports(bool p_swap_buffers); bool free(RID p_rid); diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp index 7e5ccee0e3..51ff009eaf 100644 --- a/servers/rendering/rendering_server_default.cpp +++ b/servers/rendering/rendering_server_default.cpp @@ -69,8 +69,6 @@ void RenderingServerDefault::request_frame_drawn_callback(const Callable &p_call } void RenderingServerDefault::_draw(bool p_swap_buffers, double frame_step) { - changes = 0; - RSG::rasterizer->begin_frame(frame_step); TIMESTAMP_BEGIN() @@ -102,19 +100,11 @@ void RenderingServerDefault::_draw(bool p_swap_buffers, double frame_step) { RSG::canvas->update_visibility_notifiers(); RSG::scene->update_visibility_notifiers(); - while (frame_drawn_callbacks.front()) { - Callable c = frame_drawn_callbacks.front()->get(); - Variant result; - Callable::CallError ce; - c.callp(nullptr, 0, result, ce); - if (ce.error != Callable::CallError::CALL_OK) { - String err = Variant::get_callable_error_text(c, nullptr, 0, ce); - ERR_PRINT("Error calling frame drawn function: " + err); - } - - frame_drawn_callbacks.pop_front(); + if (create_thread) { + callable_mp(this, &RenderingServerDefault::_run_post_draw_steps).call_deferred(); + } else { + _run_post_draw_steps(); } - RS::get_singleton()->emit_signal(SNAME("frame_post_draw")); if (RSG::utilities->get_captured_timestamps_count()) { Vector<FrameProfileArea> new_profile; @@ -194,6 +184,23 @@ void RenderingServerDefault::_draw(bool p_swap_buffers, double frame_step) { RSG::utilities->update_memory_info(); } +void RenderingServerDefault::_run_post_draw_steps() { + while (frame_drawn_callbacks.front()) { + Callable c = frame_drawn_callbacks.front()->get(); + Variant result; + Callable::CallError ce; + c.callp(nullptr, 0, result, ce); + if (ce.error != Callable::CallError::CALL_OK) { + String err = Variant::get_callable_error_text(c, nullptr, 0, ce); + ERR_PRINT("Error calling frame drawn function: " + err); + } + + frame_drawn_callbacks.pop_front(); + } + + emit_signal(SNAME("frame_post_draw")); +} + double RenderingServerDefault::get_frame_setup_time_cpu() const { return frame_setup_time; } @@ -203,7 +210,25 @@ bool RenderingServerDefault::has_changed() const { } void RenderingServerDefault::_init() { + RSG::threaded = create_thread; + + RSG::canvas = memnew(RendererCanvasCull); + RSG::viewport = memnew(RendererViewport); + RendererSceneCull *sr = memnew(RendererSceneCull); + RSG::camera_attributes = memnew(RendererCameraAttributes); + RSG::scene = sr; + RSG::rasterizer = RendererCompositor::create(); + RSG::utilities = RSG::rasterizer->get_utilities(); RSG::rasterizer->initialize(); + RSG::light_storage = RSG::rasterizer->get_light_storage(); + RSG::material_storage = RSG::rasterizer->get_material_storage(); + RSG::mesh_storage = RSG::rasterizer->get_mesh_storage(); + RSG::particles_storage = RSG::rasterizer->get_particles_storage(); + RSG::texture_storage = RSG::rasterizer->get_texture_storage(); + RSG::gi = RSG::rasterizer->get_gi(); + RSG::fog = RSG::rasterizer->get_fog(); + RSG::canvas_render = RSG::rasterizer->get_canvas(); + sr->set_scene_render(RSG::rasterizer->get_scene()); } void RenderingServerDefault::_finish() { @@ -212,26 +237,38 @@ void RenderingServerDefault::_finish() { } RSG::canvas->finalize(); + memdelete(RSG::canvas); RSG::rasterizer->finalize(); + memdelete(RSG::viewport); + memdelete(RSG::rasterizer); + memdelete(RSG::scene); + memdelete(RSG::camera_attributes); } void RenderingServerDefault::init() { if (create_thread) { print_verbose("RenderingServerWrapMT: Starting render thread"); DisplayServer::get_singleton()->release_rendering_thread(); - server_task_id = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &RenderingServerDefault::_thread_loop), true); + WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &RenderingServerDefault::_thread_loop), true); + command_queue.set_pump_task_id(tid); + command_queue.push(this, &RenderingServerDefault::_assign_mt_ids, tid); + command_queue.push_and_sync(this, &RenderingServerDefault::_init); + DEV_ASSERT(server_task_id == tid); } else { + server_thread = Thread::MAIN_ID; _init(); } } void RenderingServerDefault::finish() { if (create_thread) { + command_queue.push(this, &RenderingServerDefault::_finish); command_queue.push(this, &RenderingServerDefault::_thread_exit); if (server_task_id != WorkerThreadPool::INVALID_TASK_ID) { WorkerThreadPool::get_singleton()->wait_for_task_completion(server_task_id); server_task_id = WorkerThreadPool::INVALID_TASK_ID; } + server_thread = Thread::MAIN_ID; } else { _finish(); } @@ -268,17 +305,12 @@ Vector<RenderingServer::FrameProfileArea> RenderingServerDefault::get_frame_prof /* TESTING */ -void RenderingServerDefault::set_boot_image(const Ref<Image> &p_image, const Color &p_color, bool p_scale, bool p_use_filter) { - redraw_request(); - RSG::rasterizer->set_boot_image(p_image, p_color, p_scale, p_use_filter); -} - Color RenderingServerDefault::get_default_clear_color() { return RSG::texture_storage->get_default_clear_color(); } void RenderingServerDefault::set_default_clear_color(const Color &p_color) { - RSG::viewport->set_default_clear_color(p_color); + RSG::texture_storage->set_default_clear_color(p_color); } #ifndef DISABLE_DEPRECATED @@ -327,29 +359,23 @@ Size2i RenderingServerDefault::get_maximum_viewport_size() const { } } -void RenderingServerDefault::_thread_exit() { - exit = true; +void RenderingServerDefault::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) { + server_thread = Thread::get_caller_id(); + server_task_id = p_pump_task_id; } -void RenderingServerDefault::_thread_draw(bool p_swap_buffers, double frame_step) { - _draw(p_swap_buffers, frame_step); +void RenderingServerDefault::_thread_exit() { + exit = true; } void RenderingServerDefault::_thread_loop() { - server_thread = Thread::get_caller_id(); - DisplayServer::get_singleton()->gl_window_make_current(DisplayServer::MAIN_WINDOW_ID); // Move GL to this thread. - _init(); - command_queue.set_pump_task_id(server_task_id); while (!exit) { WorkerThreadPool::get_singleton()->yield(); command_queue.flush_all(); } - command_queue.flush_all(); - - _finish(); DisplayServer::get_singleton()->release_rendering_thread(); } @@ -366,7 +392,9 @@ void RenderingServerDefault::set_physics_interpolation_enabled(bool p_enabled) { /* EVENT QUEUING */ void RenderingServerDefault::sync() { - if (!create_thread) { + if (create_thread) { + command_queue.sync(); + } else { command_queue.flush_all(); // Flush all pending from other threads. } } @@ -375,8 +403,9 @@ void RenderingServerDefault::draw(bool p_swap_buffers, double frame_step) { ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Manually triggering the draw function from the RenderingServer can only be done on the main thread. Call this function from the main thread or use call_deferred()."); // Needs to be done before changes is reset to 0, to not force the editor to redraw. RS::get_singleton()->emit_signal(SNAME("frame_pre_draw")); + changes = 0; if (create_thread) { - command_queue.push(this, &RenderingServerDefault::_thread_draw, p_swap_buffers, frame_step); + command_queue.push(this, &RenderingServerDefault::_draw, p_swap_buffers, frame_step); } else { _draw(p_swap_buffers, frame_step); } @@ -390,36 +419,7 @@ RenderingServerDefault::RenderingServerDefault(bool p_create_thread) { RenderingServer::init(); create_thread = p_create_thread; - if (!create_thread) { - server_thread = Thread::MAIN_ID; - } - - RSG::threaded = create_thread; - - RSG::canvas = memnew(RendererCanvasCull); - RSG::viewport = memnew(RendererViewport); - RendererSceneCull *sr = memnew(RendererSceneCull); - RSG::camera_attributes = memnew(RendererCameraAttributes); - RSG::scene = sr; - RSG::rasterizer = RendererCompositor::create(); - RSG::utilities = RSG::rasterizer->get_utilities(); - RSG::light_storage = RSG::rasterizer->get_light_storage(); - RSG::material_storage = RSG::rasterizer->get_material_storage(); - RSG::mesh_storage = RSG::rasterizer->get_mesh_storage(); - RSG::particles_storage = RSG::rasterizer->get_particles_storage(); - RSG::texture_storage = RSG::rasterizer->get_texture_storage(); - RSG::gi = RSG::rasterizer->get_gi(); - RSG::fog = RSG::rasterizer->get_fog(); - RSG::canvas_render = RSG::rasterizer->get_canvas(); - sr->set_scene_render(RSG::rasterizer->get_scene()); - - frame_profile_frame = 0; } RenderingServerDefault::~RenderingServerDefault() { - memdelete(RSG::canvas); - memdelete(RSG::viewport); - memdelete(RSG::rasterizer); - memdelete(RSG::scene); - memdelete(RSG::camera_attributes); } diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index bb2f3d94ce..d0b6bc492d 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -63,7 +63,7 @@ class RenderingServerDefault : public RenderingServer { static void _changes_changed() {} - uint64_t frame_profile_frame; + uint64_t frame_profile_frame = 0; Vector<FrameProfileArea> frame_profile; double frame_setup_time = 0; @@ -76,18 +76,17 @@ class RenderingServerDefault : public RenderingServer { mutable CommandQueueMT command_queue; - void _thread_loop(); - - Thread::ID server_thread = Thread::UNASSIGNED_ID; + Thread::ID server_thread = Thread::MAIN_ID; WorkerThreadPool::TaskID server_task_id = WorkerThreadPool::INVALID_TASK_ID; bool exit = false; bool create_thread = false; - void _thread_draw(bool p_swap_buffers, double frame_step); - + void _assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id); void _thread_exit(); + void _thread_loop(); void _draw(bool p_swap_buffers, double frame_step); + void _run_post_draw_steps(); void _init(); void _finish(); @@ -998,9 +997,22 @@ public: FUNC1(global_shader_parameters_load_settings, bool) FUNC0(global_shader_parameters_clear) + /* COMPOSITOR */ + #undef server_name #undef ServerName +#define ServerName RendererCompositor +#define server_name RSG::rasterizer + + FUNC4S(set_boot_image, const Ref<Image> &, const Color &, bool, bool) + /* STATUS INFORMATION */ + +#undef server_name +#undef ServerName + + /* UTILITIES */ + #define ServerName RendererUtilities #define server_name RSG::utilities FUNC0RC(String, get_video_adapter_name) @@ -1056,7 +1068,7 @@ public: virtual void call_on_render_thread(const Callable &p_callable) override { if (Thread::get_caller_id() == server_thread) { command_queue.flush_if_pending(); - _call_on_render_thread(p_callable); + p_callable.call(); } else { command_queue.push(this, &RenderingServerDefault::_call_on_render_thread, p_callable); } @@ -1066,7 +1078,6 @@ public: virtual double get_frame_setup_time_cpu() const override; - virtual void set_boot_image(const Ref<Image> &p_image, const Color &p_color, bool p_scale, bool p_use_filter = true) override; virtual Color get_default_clear_color() override; virtual void set_default_clear_color(const Color &p_color) override; |