diff --git a/.github/composite/godot-install/action.yml b/.github/composite/godot-install/action.yml index 81d785b0c..3f368e911 100644 --- a/.github/composite/godot-install/action.yml +++ b/.github/composite/godot-install/action.yml @@ -31,7 +31,7 @@ runs: # - name: "Check cache for installed Godot version" # id: "cache-godot" -# uses: actions/cache@v3 +# uses: actions/cache@v4 # with: # path: ${{ runner.temp }}/godot_bin # key: ${{ inputs.artifact-name }}-v${{ inputs.godot-ver }} diff --git a/.github/composite/llvm/action.yml b/.github/composite/llvm/action.yml index c864f9211..c3405a018 100644 --- a/.github/composite/llvm/action.yml +++ b/.github/composite/llvm/action.yml @@ -26,7 +26,7 @@ runs: id: cache-llvm # Note: conditionals not yet supported; see https://github.com/actions/runner/issues/834 # if: inputs.llvm == 'true' - uses: actions/cache@v3 + uses: actions/cache@v4 with: # path: | # C:/Program Files/LLVM diff --git a/.github/workflows/full-ci.yml b/.github/workflows/full-ci.yml index f5cd61c9f..4568e53a1 100644 --- a/.github/workflows/full-ci.yml +++ b/.github/workflows/full-ci.yml @@ -365,7 +365,7 @@ jobs: godot-binary: ${{ matrix.godot-binary }} godot-args: ${{ matrix.godot-args }} # currently unused godot-prebuilt-patch: ${{ matrix.godot-prebuilt-patch }} - rust-extra-args: ${{ matrix.rust-extra-args }} --features godot/codegen-full + rust-extra-args: ${{ matrix.rust-extra-args }} rust-toolchain: ${{ matrix.rust-toolchain || 'stable' }} rust-env-rustflags: ${{ matrix.rust-env-rustflags }} rust-target: ${{ matrix.rust-target }} diff --git a/.gitignore b/.gitignore index aa8a3eb36..a4a6a0cee 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ Cargo.lock # Needed to run projects without having to open the editor first. !**/.godot/extension_list.cfg -!**/.godot/global_script_class_cache.cfg # This project: input JSONs and code generation gen diff --git a/examples/dodge-the-creeps/godot/.godot/global_script_class_cache.cfg b/examples/dodge-the-creeps/godot/.godot/global_script_class_cache.cfg deleted file mode 100644 index 1775a2f19..000000000 --- a/examples/dodge-the-creeps/godot/.godot/global_script_class_cache.cfg +++ /dev/null @@ -1 +0,0 @@ -list=Array[Dictionary]([]) diff --git a/examples/hot-reload/godot/.godot/global_script_class_cache.cfg b/examples/hot-reload/godot/.godot/global_script_class_cache.cfg deleted file mode 100644 index 1775a2f19..000000000 --- a/examples/hot-reload/godot/.godot/global_script_class_cache.cfg +++ /dev/null @@ -1 +0,0 @@ -list=Array[Dictionary]([]) diff --git a/examples/hot-reload/godot/test/ReloadOrchestrator.gd b/examples/hot-reload/godot/test/ReloadOrchestrator.gd new file mode 100644 index 000000000..7a3450bb5 --- /dev/null +++ b/examples/hot-reload/godot/test/ReloadOrchestrator.gd @@ -0,0 +1,120 @@ +# Copyright (c) godot-rust; Bromeon and contributors. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +# See https://docs.godotengine.org/en/stable/tutorials/editor/command_line_tutorial.html#running-a-script. + +class_name ReloadOrchestrator +extends SceneTree + +const TIMEOUT_S = 30 +const TO_GODOT_PORT = 1337 +const FROM_GODOT_PORT = 1338 + +var run_main_loop: bool = false +var elapsed: float = 0.0 +var udp := PacketPeerUDP.new() +var args: Array # must be Array for match, not PackedStringArray. + +func _initialize(): + args = OS.get_cmdline_user_args() + print("[GD Orch] Start ", args) + + var ok := true + match args: + ["await"]: + ok = receive_udp() + run_main_loop = true + + ["replace"]: + print("[GD Orch] Replace source code...") + ok = replace_line("../../rust/src/lib.rs") + + ["notify"]: + print("[GD Orch] Notify Godot about change...") + ok = send_udp() + + _: + fail("Invalid command-line args") + ok = false + + if not ok: + quit(1) + return + + +func _finalize(): + udp.close() + print("[GD Orch] Stop ", args) + +func _process(delta: float) -> bool: + if not run_main_loop: + return true + + elapsed += delta + if elapsed > TIMEOUT_S: + fail(str("Timed out waiting for Godot (", TIMEOUT_S, " seconds).")) + return true + + if udp.get_available_packet_count() == 0: + return false + + var packet = udp.get_packet().get_string_from_ascii() + print("[GD Orch] Received UDP packet [", packet.length(), "]: ", packet) + return true + + +func replace_line(file_path: String) -> bool: + var file = FileAccess.open(file_path, FileAccess.READ) + if file == null: + return false + + var lines = [] + while not file.eof_reached(): + lines.append(file.get_line()) + file.close() + + var replaced = 0 + file = FileAccess.open(file_path, FileAccess.WRITE) + for line: String in lines: + if line.strip_edges() == 'fn get_number(&self) -> i64 { 100 }': + file.store_line(line.replace("100", "777")) + replaced += 1 + else: + file.store_line(line) + file.close() + + if replaced == 0: + fail("Line not found in file.") + return false + else: + return true + + +func receive_udp() -> bool: + if udp.bind(FROM_GODOT_PORT) != OK: + fail("Failed to bind UDP") + return false + + print("[GD Orch] Waiting for Godot to be ready (UDP)...") + return true + + +func send_udp() -> bool: + var out_udp = PacketPeerUDP.new() + if out_udp.set_dest_address("127.0.0.1", TO_GODOT_PORT) != OK: + fail("Failed to set destination address") + return false + + if out_udp.put_packet("reload".to_utf8_buffer()) != OK: + fail("Failed to send packet") + return false + + print("[GD Orch] Packet sent successfully") + return true + + +func fail(s: String) -> void: + print("::error::[GD Orch] ", s) # GitHub Action syntax + quit(1) diff --git a/examples/hot-reload/godot/test/ReloadTest.gd b/examples/hot-reload/godot/test/ReloadTest.gd index 557e56657..9c3075854 100644 --- a/examples/hot-reload/godot/test/ReloadTest.gd +++ b/examples/hot-reload/godot/test/ReloadTest.gd @@ -15,13 +15,13 @@ var extension_name: String func _ready() -> void: - print("[GDScript] Start...") + print("[GD Editor] Start...") var r = Reloadable.new() var num = r.get_number() r.free() - print("[GDScript] Sanity check: initial number is ", num) + print("[GD Editor] Sanity check: initial number is ", num) var extensions = GDExtensionManager.get_loaded_extensions() if extensions.size() == 1: @@ -31,17 +31,14 @@ func _ready() -> void: return udp.bind(1337) - print("[GDScript] ReloadTest ready to receive...") + print("[GD Editor] ReloadTest ready to receive...") send_udp() func send_udp(): - # Attempt to bind the UDP socket to any available port for sending. - # You can specify a port number instead of 0 if you need to bind to a specific port. var out_udp = PacketPeerUDP.new() - # Set the destination address and port for the message if out_udp.set_dest_address("127.0.0.1", 1338) != OK: fail("Failed to set destination address") return @@ -50,12 +47,12 @@ func send_udp(): fail("Failed to send packet") return - print("[GDScript] Packet sent successfully") + print("[GD Editor] Packet sent successfully") out_udp.close() func _exit_tree() -> void: - print("[GDScript] ReloadTest exit.") + print("[GD Editor] ReloadTest exit.") udp.close() @@ -64,7 +61,7 @@ func _process(delta: float) -> void: return var packet = udp.get_packet().get_string_from_ascii() - print("[GDScript] Received UDP packet [", packet.length(), "]: ", packet) + print("[GD Editor] Received UDP packet [", packet.length(), "]: ", packet) if not _hot_reload(): return @@ -74,7 +71,7 @@ func _process(delta: float) -> void: r.free() if num == 777: - print("[GDScript] Successful hot-reload! Exit...") + print("[GD Editor] Successful hot-reload! Exit...") get_tree().quit(0) else: fail(str("Number was not updated correctly (is ", num, ")")) @@ -82,7 +79,6 @@ func _process(delta: float) -> void: func _hot_reload(): - # TODO sometimes fails because .so is not found var status = GDExtensionManager.reload_extension(extension_name) if status != OK: fail(str("Failed to reload extension: ", status)) @@ -92,7 +88,7 @@ func _hot_reload(): func fail(s: String) -> void: - print("::error::[GDScript] ", s) # GitHub Action syntax + print("::error::[GD Editor] ", s) # GitHub Action syntax get_tree().quit(1) diff --git a/examples/hot-reload/godot/test/orchestrate.py b/examples/hot-reload/godot/test/orchestrate.py deleted file mode 100644 index 0daca434f..000000000 --- a/examples/hot-reload/godot/test/orchestrate.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (c) godot-rust; Bromeon and contributors. -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at https://mozilla.org/MPL/2.0/. - -# Python code because it runs outside the engine, and portably sending UDP in bash is cumbersome. - -import select -import socket -import sys - - -def replace_line(file_path: str): - with open(file_path, 'r') as file: - lines = file.readlines() - - replaced = 0 - with open(file_path, 'w') as file: - for line in lines: - if line.strip() == 'fn get_number(&self) -> i64 { 100 }': - file.write(line.replace('100', '777')) - replaced += 1 - else: - file.write(line) - - if replaced == 0: - print("[Python] ERROR: Line not found in file.") - return False - else: - return True - - -def send_udp(): - msg = bytes("reload", "utf-8") - udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - udp.sendto(msg, ("localhost", 1337)) - return True - - -def receive_udp() -> bool: - timeout = 30 - udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - udp.bind(("localhost", 1338)) - - ready = select.select([udp], [], [], 20) - if ready[0]: - # If data is ready, receive it (max 1024 bytes) - data, addr = udp.recvfrom(1024) - print(f"[Python] Await ready; received from {addr}: {data}") - return True - else: - # If no data arrives within the timeout, exit with code 1 - print(f"[Python] ERROR: Await timeout: no packet within {timeout} seconds.") - return False - - -# ----------------------------------------------------------------------------------------------------------------------------------------------- -# Main - -if len(sys.argv) != 2: - print(f"Usage: {sys.argv[0]} [replace|notify]") - sys.exit(2) - -# Get the command from the command line -command = sys.argv[1] - -# Dispatch based on the command -if command == 'await': - print("[Python] Await Godot to be ready...") - ok = receive_udp() -elif command == 'replace': - print("[Python] Replace source code...") - ok = replace_line("../../rust/src/lib.rs") -elif command == 'notify': - print("[Python] Notify Godot about change...") - ok = send_udp() -else: - print("[Python] ERROR: Invalid command.") - sys.exit(2) - -if not ok: - sys.exit(1) - -print("[Python] Done.") diff --git a/examples/hot-reload/godot/test/run-test.sh b/examples/hot-reload/godot/test/run-test.sh index 2d6da7670..c160680e6 100755 --- a/examples/hot-reload/godot/test/run-test.sh +++ b/examples/hot-reload/godot/test/run-test.sh @@ -5,18 +5,22 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. # Restore un-reloaded files on exit (for local testing). +cleanedUp=0 # avoid recursion if cleanup fails cleanup() { - echo "[Bash] Cleanup..." - git checkout --quiet ../../rust/src/lib.rs ../rust.gdextension ../MainScene.tscn + if [[ $cleanedUp -eq 0 ]]; then + cleanedUp=1 + echo "[Bash] Cleanup..." + git checkout --quiet ../../rust/src/lib.rs ../rust.gdextension ../MainScene.tscn || true # ignore errors here + fi } set -euo pipefail trap cleanup EXIT -echo "[Bash] Start hot-reload integration test..." +echo "[Bash] Start hot-reload integration test..." # Restore un-reloaded file (for local testing). -git checkout --quiet ../../rust/src/lib.rs ../rust.gdextension +git checkout --quiet ../../rust/src/lib.rs ../rust.gdextension # Set up editor file which has scene open, so @tool script loads at startup. Also copy scene file that holds a script. mkdir -p ../.godot/editor @@ -33,20 +37,20 @@ sleep 0.5 $GODOT4_BIN -e --headless --path .. & pid=$! -echo "[Bash] Wait for Godot ready (PID $pid)..." +echo "[Bash] Wait for Godot ready (PID $pid)..." -python orchestrate.py await -python orchestrate.py replace +$GODOT4_BIN --headless --no-header --script ReloadOrchestrator.gd -- await +$GODOT4_BIN --headless --no-header --script ReloadOrchestrator.gd -- replace # Compile updated Rust source. cargo build -p hot-reload $cargoArgs -python orchestrate.py notify +$GODOT4_BIN --headless --no-header --script ReloadOrchestrator.gd -- notify -echo "[Bash] Wait for Godot exit..." +echo "[Bash] Wait for Godot exit..." wait $pid status=$? -echo "[Bash] Godot (PID $pid) has completed with status $status." +echo "[Bash] Godot (PID $pid) has completed with status $status." diff --git a/examples/hot-reload/rust/src/lib.rs b/examples/hot-reload/rust/src/lib.rs index b4fdf1879..d510d7214 100644 --- a/examples/hot-reload/rust/src/lib.rs +++ b/examples/hot-reload/rust/src/lib.rs @@ -12,11 +12,11 @@ struct HotReload; #[gdextension] unsafe impl ExtensionLibrary for HotReload { fn on_level_init(_level: InitLevel) { - println!("[Rust] Init level {:?}", _level); + println!("[Rust] Init level {:?}", _level); } fn on_level_deinit(_level: InitLevel) { - println!("[Rust] Deinit level {:?}", _level); + println!("[Rust] Deinit level {:?}", _level); } } diff --git a/godot-codegen/src/special_cases/special_cases.rs b/godot-codegen/src/special_cases/special_cases.rs index 74ef71d6f..2a160f5ed 100644 --- a/godot-codegen/src/special_cases/special_cases.rs +++ b/godot-codegen/src/special_cases/special_cases.rs @@ -52,6 +52,16 @@ pub fn is_class_method_deleted(class_name: &TyName, method: &JsonClassMethod, ct | ("ResourceLoader", "load_threaded_request") // also: enum ThreadLoadStatus + // TODO: Godot exposed methods that are unavailable, bug reported in https://github.com/godotengine/godot/issues/90303. + | ("OpenXRHand", "set_hand_skeleton") + | ("OpenXRHand", "get_hand_skeleton") + | ("SkeletonIK3D", "set_interpolation") + | ("SkeletonIK3D", "get_interpolation") + | ("VisualShaderNodeComment", "set_title") + | ("VisualShaderNodeComment", "get_title") + | ("VisualShaderNodeComment", "set_description") + | ("VisualShaderNodeComment", "get_description") + => true, _ => false } }