diff --git a/.cargo/config.toml b/.cargo/config.toml index da2da23b..acedb593 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,4 +2,4 @@ [target.armv7-unknown-linux-gnueabihf] linker = "arm-linux-gnueabihf-gcc" [target.aarch64-unknown-linux-gnu] -linker = "aarch64-linux-gnu-gcc" +linker = "aarch64-linux-gnu-gcc" \ No newline at end of file diff --git a/.github/actions/build-plugin/action.yml b/.github/actions/build-plugin/action.yml index 91fea27f..19b44e5d 100644 --- a/.github/actions/build-plugin/action.yml +++ b/.github/actions/build-plugin/action.yml @@ -30,11 +30,15 @@ runs: rustup target add ${{ inputs.arch }} - name: Build for Windows and Linux shell: sh + env: + CI: 1 if: ${{ inputs.arch != 'x86_64-apple-darwin'}} run: | just build-patchwork release ${{ inputs.arch }} - name: Build for Mac shell: sh + env: + CI: 1 if: ${{ inputs.arch == 'x86_64-apple-darwin'}} run: | just build-patchwork release "all-apple-darwin" diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b29124e6..7f7282b0 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,7 @@ "rust-lang.rust-analyzer", "tamasfe.even-better-toml", "nefrob.vscode-just-syntax", + "llvm-vs-code-extensions.lldb-dap" // TODO: enable this once they implement a project picker // "geequlim.godot-tools" ] diff --git a/.vscode/launch.json b/.vscode/launch.json index fdd68ea3..83e3991d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,9 +21,9 @@ "type": "lldb", "request": "launch", "program": "", - "osx": { "program": "${workspaceFolder}/bin/godot.macos.editor.arm64" }, + "osx": { "program": "${workspaceFolder}/build/godot/bin/godot_macos_editor.app/Contents/MacOS/Godot" }, "windows": { "program": "${workspaceFolder}/build/godot/bin/godot.windows.editor.x86_64.exe" }, - "linux": { "program": "${workspaceFolder}/bin/godot.linux.editor.x86_64" }, + "linux": { "program": "${workspaceFolder}/build/godot/bin/godot.linux.editor.x86_64" }, "args": [ "-e", "--path", @@ -41,9 +41,9 @@ "type": "lldb", "request": "launch", "program": "", - "osx": { "program": "${workspaceFolder}/bin/godot.macos.editor.dev.arm64" }, + "osx": { "program": "build/godot/bin/godot_macos_editor_dev.app/Contents/MacOS/Godot" }, "windows": { "program": "${workspaceFolder}/build/godot/bin/godot.windows.editor.dev.x86_64.exe" }, - "linux": { "program": "${workspaceFolder}/bin/godot.linux.editor.dev.x86_64" }, + "linux": { "program": "${workspaceFolder}/build/godot/bin/godot.linux.editor.dev.x86_64" }, "args": [ "-e", "--path", @@ -61,9 +61,9 @@ "type": "cppvsdbg", "request": "launch", "program": "", - "osx": { "program": "${workspaceFolder}/bin/godot.macos.editor.arm64" }, + "osx": { "program": "${workspaceFolder}/build/godot/bin/godot_macos_editor.app/Contents/MacOS/Godot" }, "windows": { "program": "${workspaceFolder}/build/godot/bin/godot.windows.editor.x86_64.exe" }, - "linux": { "program": "${workspaceFolder}/bin/godot.linux.editor.x86_64" }, + "linux": { "program": "${workspaceFolder}/build/godot/bin/godot.linux.editor.x86_64" }, "args": [ "-e", "--path", @@ -80,9 +80,9 @@ "type": "cppvsdbg", "request": "launch", "program": "", - "osx": { "program": "${workspaceFolder}/bin/godot.macos.editor.dev.arm64" }, + "osx": { "program": "build/godot/bin/godot_macos_editor_dev.app/Contents/MacOS/Godot" }, "windows": { "program": "${workspaceFolder}/build/godot/bin/godot.windows.editor.dev.x86_64.exe" }, - "linux": { "program": "${workspaceFolder}/bin/godot.linux.editor.dev.x86_64" }, + "linux": { "program": "${workspaceFolder}/build/godot/bin/godot.linux.editor.dev.x86_64" }, "args": [ "-e", "--path", @@ -98,7 +98,7 @@ "name": "macOS: Launch moddable-platformer", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/bin/godot.macos.editor.arm64", + "program": "${workspaceFolder}/build/godot/bin/godot_macos_editor.app/Contents/MacOS/Godot", "args": [ "-e", "--path", @@ -116,9 +116,9 @@ "type": "cppdbg", "request": "launch", "program": "", - "osx": { "program": "${workspaceFolder}/bin/godot.macos.editor.dev.arm64" }, + "osx": { "program": "build/godot/bin/godot_macos_editor_dev.app/Contents/MacOS/Godot" }, "windows": { "program": "${workspaceFolder}/build/godot/bin/godot.windows.editor.dev.x86_64.exe" }, - "linux": { "program": "${workspaceFolder}/bin/godot.linux.editor.dev.x86_64" }, + "linux": { "program": "${workspaceFolder}/build/godot/bin/godot.linux.editor.dev.x86_64" }, "args": [ "-e", "--path", @@ -134,7 +134,7 @@ "name": "Linux: Launch moddable-platformer", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/bin/godot.macos.editor.arm64", + "program": "${workspaceFolder}/build/godot/bin/godot_macos_editor.app/Contents/MacOS/Godot", "args": [ "-e", "--path", @@ -151,9 +151,9 @@ "type": "cppdbg", "request": "launch", "program": "", - "osx": { "program": "${workspaceFolder}/bin/godot.macos.editor.dev.arm64" }, + "osx": { "program": "build/godot/bin/godot_macos_editor_dev.app/Contents/MacOS/Godot" }, "windows": { "program": "${workspaceFolder}/build/godot/bin/godot.windows.editor.dev.x86_64.exe" }, - "linux": { "program": "${workspaceFolder}/bin/godot.linux.editor.dev.x86_64" }, + "linux": { "program": "${workspaceFolder}/build/godot/bin/godot.linux.editor.dev.x86_64" }, "args": [ "-e", "--path", @@ -165,5 +165,46 @@ "preLaunchTask": "Build: Prepare moddable-platformer (Debug)", "MIMode": "gdb" }, + { + "name": "lldb-dap: Launch moddable-platformer", + "type": "lldb-dap", + "request": "launch", + "program": "", + "osx": { "program": "${workspaceFolder}/build/godot/bin/godot_macos_editor.app/Contents/MacOS/Godot" }, + "windows": { "program": "${workspaceFolder}/build/godot/bin/godot.windows.editor.x86_64.exe" }, + "linux": { "program": "${workspaceFolder}/build/godot/bin/godot.linux.editor.x86_64" }, + "args": [ + "-e", + "--path", + "${workspaceFolder}/build/moddable-platformer", + ], + "console": "integratedTerminal", + "cwd": "${workspaceFolder}", + "initCommands": [ + "command script import ${workspaceFolder}/build/GodotFormatters/godot_formatters", + ], + "preLaunchTask": "Build: Prepare moddable-platformer (Release)" + }, + { + "name": "lldb-dap: Launch moddable-platformer (dev)", + "type": "lldb-dap", + "request": "launch", + "program": "", + "osx": { "program": "build/godot/bin/godot_macos_editor_dev.app/Contents/MacOS/Godot" }, + "windows": { "program": "${workspaceFolder}/build/godot/bin/godot.windows.editor.dev.x86_64.exe" }, + "linux": { "program": "${workspaceFolder}/build/godot/bin/godot.linux.editor.dev.x86_64" }, + "args": [ + "-e", + "--path", + "${workspaceFolder}/build/moddable-platformer", + ], + "console": "integratedTerminal", + "cwd": "${workspaceFolder}", + "initCommands": [ + "command script import ${workspaceFolder}/build/GodotFormatters/godot_formatters", + ], + "preLaunchTask": "Build: Prepare moddable-platformer (Debug)" + }, + ] } diff --git a/Cargo.lock b/Cargo.lock index 652f1468..a3734db4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "async-channel" version = "2.5.0" @@ -88,6 +94,45 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" @@ -121,8 +166,7 @@ dependencies = [ [[package]] name = "autosurgeon" version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b6547a9a35fbf5e5b1305a56c42196ae2cfb456897f1cab97d4829f1096550" +source = "git+https://github.com/nikitalita/autosurgeon.git?branch=with-tuples#7946da4fa575bf6d53c803d95d49c8c07440e3b5" dependencies = [ "automerge", "autosurgeon-derive", @@ -133,8 +177,7 @@ dependencies = [ [[package]] name = "autosurgeon-derive" version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f99b54090e5aca6915313efcf065f9873c9ca9d39c875cfc956f30523075d49f" +source = "git+https://github.com/nikitalita/autosurgeon.git?branch=with-tuples#7946da4fa575bf6d53c803d95d49c8c07440e3b5" dependencies = [ "proc-macro2", "quote", @@ -142,12 +185,61 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -214,6 +306,12 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.0" @@ -339,6 +437,46 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console-api" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8599749b6667e2f0c910c1d0dff6901163ff698a52d5a39720f61b5be4b20d3" +dependencies = [ + "futures-core", + "prost", + "prost-types", + "tonic", + "tonic-prost", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4915b7d8dd960457a1b6c380114c2944f728e7c65294ab247ae6b6f1f37592" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures-task", + "hdrhistogram", + "humantime", + "hyper-util", + "prost", + "prost-types", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "const-oid" version = "0.10.1" @@ -585,6 +723,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -810,12 +954,44 @@ dependencies = [ "venial", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64 0.21.7", + "byteorder", + "flate2", + "nom", + "num-traits", +] + [[package]] name = "heck" version = "0.4.1" @@ -854,12 +1030,47 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + [[package]] name = "hybrid-array" version = "0.4.5" @@ -869,6 +1080,63 @@ dependencies = [ "typenum", ] +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.64" @@ -895,9 +1163,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -986,6 +1254,72 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[package]] +name = "lexical" +version = "7.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc8a009b2ff1f419ccc62706f04fe0ca6e67b37460513964a3dfdb919bb37d6" +dependencies = [ + "lexical-core", +] + +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + [[package]] name = "libc" version = "0.2.178" @@ -1049,12 +1383,30 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minicbor" version = "1.1.0" @@ -1075,6 +1427,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1112,6 +1470,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a846cbc04412cf509efcd8f3694b114fc700a035fb5a37f21517f9fb019f1ebc" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notify" version = "8.0.0" @@ -1222,16 +1590,23 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" name = "patchwork_rust" version = "0.0.1" dependencies = [ + "async-stream", "automerge", "autosurgeon", + "base64 0.22.1", "cargo-post", "cbindgen", "chrono", + "console-subscriber", "futures", "glob", "godot", + "indexmap", + "lexical", + "md5", "notify", "notify-debouncer-mini", + "num-traits", "pretty_assertions", "rand 0.9.2", "rayon", @@ -1245,14 +1620,41 @@ dependencies = [ "static_vcruntime", "tempfile", "tokio", + "tokio-stream", "tokio-tungstenite", + "tokio-util", "tracing", "tracing-appender", "tracing-subscriber", "tree-sitter", "tree-sitter-godot-resource", "uuid", - "yet-another-md5", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -1320,6 +1722,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.42" @@ -1578,7 +2012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bab909f3bd4ecf787b3221fd4a8b480f385b3e358e84f7ddb379c0d503aa921" dependencies = [ "automerge", - "base64", + "base64 0.21.7", "bs58", "hex", "minicbor", @@ -1857,6 +2291,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "tempfile" version = "3.23.0" @@ -1967,9 +2407,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -1979,6 +2419,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", + "tracing", "windows-sys 0.61.2", ] @@ -1993,6 +2434,18 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + [[package]] name = "tokio-tungstenite" version = "0.27.0" @@ -2007,9 +2460,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -2098,6 +2551,77 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tonic" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.43" @@ -2213,6 +2737,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae62f7eae5eb549c71b76658648b72cc6111f2d87d24a1e31fa907f4943e3ce" +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tungstenite" version = "0.27.0" @@ -2319,6 +2849,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2644,16 +3183,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -[[package]] -name = "yet-another-md5" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bba9e0849c3974605f963b44cb18cb138184e4455f9533986ec35aa469c57ea7" -dependencies = [ - "log", - "thiserror 1.0.69", -] - [[package]] name = "zerocopy" version = "0.8.31" diff --git a/Cargo.toml b/Cargo.toml index ac60eef6..c8ab6f97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ [profile.release_debug] inherits = "release" opt-level = 0 +strip = "none" debug = true lto = false panic = "abort" diff --git a/Developers.md b/Developers.md index b402731a..9bbfee11 100644 --- a/Developers.md +++ b/Developers.md @@ -45,6 +45,7 @@ project=[moddable-platformer|threadbare] # launch threadbare or moddable-platfor patchwork_profile=[release|debug] # whether we should build the rust code with release or debug configuration godot_profile=[release|debug|sani] # whether we should build Godot with release, debug, or sani configuration server_url= # force embed a server URL into the project. By default, just keeps whatever server URL is already configured in the project. +tracing_support=[none|tokio-console] # allows a tokio-console to be connected at the default port for debugging ``` #### Using Visual Studio Code diff --git a/build.env b/build.env index 5eeb0361..4fd91513 100644 --- a/build.env +++ b/build.env @@ -1,8 +1,8 @@ # Endless projects MODDABLE_PLATFORMER_REPO=endlessm/moddable-platformer -MODDABLE_PLATFORMER_REF=godot-4.6 +MODDABLE_PLATFORMER_REF=main THREADBARE_REPO=endlessm/threadbare -THREADBARE_REF=godot-4.6 +THREADBARE_REF=main # Godot GODOT_REPO=godotengine/godot.git diff --git a/editor/patchwork_editor.cpp b/editor/patchwork_editor.cpp index 2c50a6b9..9802ee53 100644 --- a/editor/patchwork_editor.cpp +++ b/editor/patchwork_editor.cpp @@ -35,75 +35,6 @@ PatchworkEditor *PatchworkEditor::get_singleton() { return singleton; } -void PatchworkEditor::_on_filesystem_changed() { -} - -void PatchworkEditor::_on_resources_reloaded() { -} - -void PatchworkEditor::_on_history_changed() { - // // get the current edited scene - // auto scene = EditorNode::get_singleton()->get_edited_scene(); - // if (scene == nullptr) { - // return; - // } - // // pack the scene into a packed scene - // auto packed_scene = memnew(PackedScene); - // packed_scene->pack(scene); - // // temp file name with random name - // auto temp_file = "user://temp_" + itos(OS::get_singleton()->get_unix_time()) + ".tscn"; - // Error err = ResourceSaver::save(packed_scene, temp_file); - // if (err != OK) { - // print_line("Error saving scene"); - // return; - // } - // // open the file - // auto file = FileAccess::open(temp_file, FileAccess::READ); - // if (file.is_valid()) { - // auto contents = file->get_as_text(); - // auto scene_path = scene->get_scene_file_path(); - // if (scene_path == "res://main.tscn") { - // fs->save_file(scene->get_scene_file_path(), contents); - // // test getting the file - // // auto file_contents = fs->get_file(scene->get_scene_file_path()); - // // if (file_contents != contents) { - // // print_line("File contents do not match"); - // // } else { - // // print_line("Yay"); - // // } - // } - // file->close(); - // } - // DirAccess::remove_absolute(temp_file); -} - -void PatchworkEditor::handle_change(const String &resource_path, const NodePath &node_path, HashMap properties) { - // auto res = ResourceLoader::load(resource_path); - // if (!node_path.is_empty()) { - // Ref scene = res; - // auto node_idx = scene->get_state()->find_node_by_path(node_path); - // } -} - -void PatchworkEditor::_on_file_changed(Dictionary dict) { - // let args = ["file_path", "res://main.tscn", - // "node_path", node_path.as_str(), - // "type", "node_deleted", - // ]; - // auto file_path = dict["file_path"]; - // auto node_path = dict["node_path"]; -} - -void PatchworkEditor::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_READY: { - print_line("Entered tree"); - break; - } - default: - break; - } -} bool PatchworkEditor::unsaved_files_open() { if (get_unsaved_scripts().size() > 0) { @@ -135,240 +66,6 @@ Vector PatchworkEditor::get_unsaved_files() { return files; } -bool PatchworkEditor::detect_utf8(const PackedByteArray &p_utf8_buf) { - int cstr_size = 0; - int str_size = 0; - const char *p_utf8 = (const char *)p_utf8_buf.ptr(); - int p_len = p_utf8_buf.size(); - if (p_len == 0) { - return true; // empty string - } - bool p_skip_cr = false; - /* HANDLE BOM (Byte Order Mark) */ - if (p_len < 0 || p_len >= 3) { - bool has_bom = uint8_t(p_utf8[0]) == 0xef && uint8_t(p_utf8[1]) == 0xbb && uint8_t(p_utf8[2]) == 0xbf; - if (has_bom) { - //8-bit encoding, byte order has no meaning in UTF-8, just skip it - if (p_len >= 0) { - p_len -= 3; - } - p_utf8 += 3; - } - } - - // bool decode_error = false; - // bool decode_failed = false; - { - const char *ptrtmp = p_utf8; - const char *ptrtmp_limit = p_len >= 0 ? &p_utf8[p_len] : nullptr; - int skip = 0; - uint8_t c_start = 0; - while (ptrtmp != ptrtmp_limit && *ptrtmp) { -#if CHAR_MIN == 0 - uint8_t c = *ptrtmp; -#else - uint8_t c = *ptrtmp >= 0 ? *ptrtmp : uint8_t(256 + *ptrtmp); -#endif - - if (skip == 0) { - if (p_skip_cr && c == '\r') { - ptrtmp++; - continue; - } - /* Determine the number of characters in sequence */ - if ((c & 0x80) == 0) { - skip = 0; - } else if ((c & 0xe0) == 0xc0) { - skip = 1; - } else if ((c & 0xf0) == 0xe0) { - skip = 2; - } else if ((c & 0xf8) == 0xf0) { - skip = 3; - } else if ((c & 0xfc) == 0xf8) { - skip = 4; - } else if ((c & 0xfe) == 0xfc) { - skip = 5; - } else { - skip = 0; - // print_unicode_error(vformat("Invalid UTF-8 leading byte (%x)", c), true); - // decode_failed = true; - return false; - } - c_start = c; - - if (skip == 1 && (c & 0x1e) == 0) { - // print_unicode_error(vformat("Overlong encoding (%x ...)", c)); - // decode_error = true; - return false; - } - str_size++; - } else { - if ((c_start == 0xe0 && skip == 2 && c < 0xa0) || (c_start == 0xf0 && skip == 3 && c < 0x90) || (c_start == 0xf8 && skip == 4 && c < 0x88) || (c_start == 0xfc && skip == 5 && c < 0x84)) { - // print_unicode_error(vformat("Overlong encoding (%x %x ...)", c_start, c)); - // decode_error = true; - return false; - } - if (c < 0x80 || c > 0xbf) { - // print_unicode_error(vformat("Invalid UTF-8 continuation byte (%x ... %x ...)", c_start, c), true); - // decode_failed = true; - return false; - - // skip = 0; - } else { - --skip; - } - } - - cstr_size++; - ptrtmp++; - } - // not checking for last sequence because we pass in incomplete bytes - // if (skip) { - // print_unicode_error(vformat("Missing %d UTF-8 continuation byte(s)", skip), true); - // decode_failed = true; - // return false; - // } - } - - if (str_size == 0) { - // clear(); - return true; // empty string - } - - // resize(str_size + 1); - // char32_t *dst = ptrw(); - // dst[str_size] = 0; - - int skip = 0; - uint32_t unichar = 0; - while (cstr_size) { -#if CHAR_MIN == 0 - uint8_t c = *p_utf8; -#else - uint8_t c = *p_utf8 >= 0 ? *p_utf8 : uint8_t(256 + *p_utf8); -#endif - - if (skip == 0) { - if (p_skip_cr && c == '\r') { - p_utf8++; - continue; - } - /* Determine the number of characters in sequence */ - if ((c & 0x80) == 0) { - // *(dst++) = c; - unichar = 0; - skip = 0; - } else if ((c & 0xe0) == 0xc0) { - unichar = (0xff >> 3) & c; - skip = 1; - } else if ((c & 0xf0) == 0xe0) { - unichar = (0xff >> 4) & c; - skip = 2; - } else if ((c & 0xf8) == 0xf0) { - unichar = (0xff >> 5) & c; - skip = 3; - } else if ((c & 0xfc) == 0xf8) { - unichar = (0xff >> 6) & c; - skip = 4; - } else if ((c & 0xfe) == 0xfc) { - unichar = (0xff >> 7) & c; - skip = 5; - } else { - // *(dst++) = _replacement_char; - // unichar = 0; - // skip = 0; - return false; - } - } else { - if (c < 0x80 || c > 0xbf) { - // *(dst++) = _replacement_char; - skip = 0; - } else { - unichar = (unichar << 6) | (c & 0x3f); - --skip; - if (skip == 0) { - if (unichar == 0) { - return false; - // print_unicode_error("NUL character", true); - // decode_failed = true; - // unichar = _replacement_char; - } else if ((unichar & 0xfffff800) == 0xd800) { - return false; - - // print_unicode_error(vformat("Unpaired surrogate (%x)", unichar), true); - // decode_failed = true; - // unichar = _replacement_char; - } else if (unichar > 0x10ffff) { - return false; - - // print_unicode_error(vformat("Invalid unicode codepoint (%x)", unichar), true); - // decode_failed = true; - // unichar = _replacement_char; - } - // *(dst++) = unichar; - } - } - } - - cstr_size--; - p_utf8++; - } - if (skip) { - // return false; - // *(dst++) = 0x20; - } - - return true; -} - -Vector PatchworkEditor::get_recursive_dir_list(const String &p_dir, const Vector &wildcards, const bool absolute, const String &rel) { - Vector ret; - Error err; - Ref da = DirAccess::open(p_dir.path_join(rel), &err); - ERR_FAIL_COND_V_MSG(da.is_null(), ret, "Failed to open directory " + p_dir); - - if (da.is_null()) { - return ret; - } - Vector dirs; - Vector files; - - String base = absolute ? p_dir : ""; - da->list_dir_begin(); - String f = da->get_next(); - while (!f.is_empty()) { - if (f == "." || f == "..") { - f = da->get_next(); - continue; - } else if (da->current_is_dir()) { - dirs.push_back(f); - } else { - files.push_back(f); - } - f = da->get_next(); - } - da->list_dir_end(); - - dirs.sort_custom(); - files.sort_custom(); - for (auto &d : dirs) { - ret.append_array(get_recursive_dir_list(p_dir, wildcards, absolute, rel.path_join(d))); - } - for (auto &file : files) { - if (wildcards.size() > 0) { - for (int i = 0; i < wildcards.size(); i++) { - if (file.get_file().matchn(wildcards[i])) { - ret.append(base.path_join(rel).path_join(file)); - break; - } - } - } else { - ret.append(base.path_join(rel).path_join(file)); - } - } - - return ret; -} void PatchworkEditor::progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) { EditorNode::get_singleton()->progress_add_task(p_task, p_label, p_steps, p_can_cancel); @@ -412,39 +109,21 @@ inline Vector _get_section_keys(const Ref &p_config_file, co #endif } -Ref PatchworkEditor::import_and_load_resource(const String &p_path) { - // get the import path - auto import_path = p_path + ".import"; - // load the import file +Error PatchworkEditor::import_and_save_resource(const String &p_path, const String &import_file_content, const String &import_base_path) { + String base_dir = import_base_path.get_base_dir(); + HashMap params; + Ref import_file; import_file.instantiate(); - Error err = import_file->load(import_path); - ERR_FAIL_COND_V_MSG(err != OK, {}, "Failed to load import file at path " + import_path); - // get the importer name - ; - HashMap params; + + Error err = import_file->parse(import_file_content); + ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to parse import file content"); String importer_name = import_file->get_value("remap", "importer"); Vector param_keys = _get_section_keys(import_file, "params"); for (auto ¶m_key : param_keys) { auto param_value = import_file->get_value("params", param_key); params[param_key] = param_value; } - String import_base_path = import_file->get_value("remap", "path", ""); - if (import_base_path.is_empty()) { - // iterate through the remap keys, find one that begins with 'path' - Vector remap_keys = _get_section_keys(import_file, "remap"); - for (auto &remap_key : remap_keys) { - if (remap_key.begins_with("path")) { - import_base_path = import_file->get_value("remap", remap_key); - break; - } - } - } - String base_dir = import_base_path.get_base_dir(); - String ext = import_base_path.get_extension(); - String file = import_base_path.get_file(); - String file_without_ext = file.get_slice(".", 0) + "." + file.get_slice(".", 1); - import_base_path = base_dir.path_join(file_without_ext); // make dir recursive DirAccess::make_dir_recursive_absolute(base_dir); @@ -461,12 +140,7 @@ Ref PatchworkEditor::import_and_load_resource(const String &p_path) { } } - err = importer->import(ResourceUID::INVALID_ID, p_path, import_base_path, params, &import_variants, &import_options, &metadata); - ERR_FAIL_COND_V_MSG(err != OK, {}, "Failed to import resource at path " + p_path); - // load the resource - auto res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP, &err); - ERR_FAIL_COND_V_MSG(err != OK, {}, "Failed to load resource at path " + import_base_path); - return res; + return importer->import(ResourceUID::INVALID_ID, p_path, import_base_path, params, &import_variants, &import_options, &metadata); } void PatchworkEditor::save_all_scenes_and_scripts() { @@ -669,16 +343,6 @@ void PatchworkEditor::close_files_if_open(const Vector &p_paths) { PatchworkEditor *PatchworkEditor::singleton = nullptr; -PatchworkEditor::PatchworkEditor(EditorNode *p_editor) { - singleton = this; - editor = p_editor; - // EditorUndoRedoManager::get_singleton()->connect(SNAME("history_changed"), callable_mp(this, &PatchworkEditor::_on_history_changed)); - // - // fs = GodotProject::create(""); - // this->add_child(fs); - // EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &PatchworkEditor::signal_callback)); -} - void PatchworkEditor::_bind_methods() { ClassDB::bind_static_method(get_class_static(), D_METHOD("progress_add_task", "task", "label", "steps", "can_cancel"), &PatchworkEditor::progress_add_task); ClassDB::bind_static_method(get_class_static(), D_METHOD("progress_task_step", "task", "state", "step", "force_refresh"), &PatchworkEditor::progress_task_step); @@ -689,11 +353,9 @@ void PatchworkEditor::_bind_methods() { ClassDB::bind_static_method(get_class_static(), D_METHOD("progress_end_task_bg", "task"), &PatchworkEditor::progress_end_task_bg); ClassDB::bind_static_method(get_class_static(), "unsaved_files_open", &PatchworkEditor::unsaved_files_open); - ClassDB::bind_static_method(get_class_static(), D_METHOD("detect_utf8", "utf8_buf"), &PatchworkEditor::detect_utf8); - ClassDB::bind_static_method(get_class_static(), D_METHOD("get_recursive_dir_list", "dir", "wildcards", "absolute", "rel"), &PatchworkEditor::get_recursive_dir_list); ClassDB::bind_static_method(get_class_static(), D_METHOD("get_importer_by_name", "name"), &PatchworkEditor::get_importer_by_name); - ClassDB::bind_static_method(get_class_static(), D_METHOD("import_and_load_resource", "path"), &PatchworkEditor::import_and_load_resource); + ClassDB::bind_static_method(get_class_static(), D_METHOD("import_and_save_resource", "path", "import_file_content", "import_base_path"), &PatchworkEditor::import_and_save_resource); ClassDB::bind_static_method(get_class_static(), D_METHOD("save_all_scenes_and_scripts"), &PatchworkEditor::save_all_scenes_and_scripts); ClassDB::bind_static_method(get_class_static(), D_METHOD("save_all_scripts"), &PatchworkEditor::save_all_scripts); diff --git a/editor/patchwork_editor.h b/editor/patchwork_editor.h index 6e126f94..11322a37 100644 --- a/editor/patchwork_editor.h +++ b/editor/patchwork_editor.h @@ -9,32 +9,23 @@ #include "scene/gui/control.h" #include "scene/main/node.h" -class PatchworkEditor : public Node { - GDCLASS(PatchworkEditor, Node); +class PatchworkEditor : public Object { + GDCLASS(PatchworkEditor, Object); private: - EditorNode *editor = nullptr; static PatchworkEditor *singleton; static void _editor_init_callback_static(); static Callable steal_close_current_script_tab_file_callback(); protected: - void _notification(int p_what); static void _bind_methods(); public: static PatchworkEditor *get_singleton(); - PatchworkEditor(EditorNode *p_editor); PatchworkEditor(); ~PatchworkEditor(); - void _on_filesystem_changed(); - void _on_resources_reloaded(); - void _on_history_changed(); - void handle_change(const String &resource_path, const NodePath &node_path, HashMap properties); - void _on_file_changed(Dictionary dict); static bool unsaved_files_open(); static bool detect_utf8(const PackedByteArray &p_utf8_buf); - static Vector get_recursive_dir_list(const String &p_dir, const Vector &wildcards = {}, bool absolute = true, const String &rel = ""); static void progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel = false); static bool progress_task_step(const String &p_task, const String &p_state, int p_step = -1, bool p_force_refresh = true); static void progress_end_task(const String &p_task); @@ -43,7 +34,9 @@ class PatchworkEditor : public Node { static void progress_task_step_bg(const String &p_task, int p_step = -1); static void progress_end_task_bg(const String &p_task); static Ref get_importer_by_name(const String &p_name); - static Ref import_and_load_resource(const String &p_path); + // TODO: remove this once the resource loader is working + static Error import_and_save_resource(const String &p_path, const String &import_file_content, const String &import_base_path); + static Vector get_unsaved_files(); static bool is_editor_importing(); diff --git a/editor/register_types.cpp b/editor/register_types.cpp index 739c98d5..d73e5f14 100644 --- a/editor/register_types.cpp +++ b/editor/register_types.cpp @@ -3,25 +3,13 @@ /*************************************************************************/ #include "register_types.h" -#include "editor/editor_node.h" #include "patchwork_editor.h" -void patchwork_editor_init_callback() { - EditorNode *editor = EditorNode::get_singleton(); - editor->add_child(memnew(PatchworkEditor(editor))); -} void initialize_patchwork_editor_module(ModuleInitializationLevel p_level) { - if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { - EditorNode::add_init_callback(&patchwork_editor_init_callback); - } if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { ClassDB::register_class(); - // register the editor inspector section } - - - } void uninitialize_patchwork_editor_module(ModuleInitializationLevel p_level) { diff --git a/justfile b/justfile index 8783e2dd..203a13cf 100644 --- a/justfile +++ b/justfile @@ -124,22 +124,65 @@ build-godot profile: _link-godot #!/usr/bin/env sh # set -euxo pipefail cd "build/godot" + EXTRA_BUILD_FLAGS="" + # check for macos; if yes, add `generate_bundle=yes` + if [[ "{{os()}}" = "macos" ]] ; then + EXTRA_BUILD_FLAGS="generate_bundle=yes" + # check for the .cargo/.devidentity file; if it exists, add `bundle_sign_identity=` + if [ -f .cargo/.devidentity ]; then + EXTRA_BUILD_FLAGS="$EXTRA_BUILD_FLAGS bundle_sign_identity=$(cat .cargo/.devidentity)" + fi + fi # TODO: figure out a way to see if scons actually needs a run, since this takes forever even when built if [[ {{profile}} = "release" ]] ; then - scons dev_build=no debug_symbols=no target=editor deprecated=yes minizip=yes compiledb=yes metal=no module_text_server_fb_enabled=yes + scons dev_build=no debug_symbols=no target=editor deprecated=yes minizip=yes compiledb=yes metal=no module_text_server_fb_enabled=yes $EXTRA_BUILD_FLAGS elif [[ {{profile}} = "sani" ]] ; then - scons dev_build=yes target=editor compiledb=yes deprecated=yes minizip=yes tests=yes use_asan=yes metal=no module_text_server_fb_enabled=yes + scons dev_build=yes target=editor compiledb=yes deprecated=yes minizip=yes tests=yes use_asan=yes metal=no module_text_server_fb_enabled=yes $EXTRA_BUILD_FLAGS else - scons dev_build=yes target=editor compiledb=yes deprecated=yes minizip=yes tests=yes metal=no module_text_server_fb_enabled=yes + scons dev_build=yes target=editor compiledb=yes deprecated=yes minizip=yes tests=yes metal=no module_text_server_fb_enabled=yes $EXTRA_BUILD_FLAGS fi # Build the Rust plugin binaries. -_build-plugin architecture profile: - cargo build --profile="{{profile}}" --target="{{architecture}}" +_build-plugin architecture profile tracing_support: + #!/usr/bin/env sh + if [[ {{tracing_support}} = "tokio-console" ]] ; then + export RUSTFLAGS="--cfg tokio_unstable" + cargo build --profile="{{profile}}" --target="{{architecture}}" --features "tokio-console" + else + cargo build --profile="{{profile}}" --target="{{architecture}}" + fi + +# Sign macOS plugin binaries using the identity from .cargo/.devidentity (one line, plain text). +@_sign-macos-plugin: + #!/usr/bin/env sh + # set -euxo pipefail + + # check for CI env variable, if it is set, skip signing + if [ "$CI" = "1" ]; then + echo "Skipping macOS plugin signing on CI" + exit 0 + fi + + if [ ! -f .cargo/.devidentity ]; then + echo "**** No development identity file found; if you want to enable code signing, create a .cargo/.devidentity file with your dev ID." + echo "**** Example: echo 'Developer ID Application: Your Name (TEAMID)' > .cargo/.devidentity" + echo "**** HINT: use 'security find-identity -p codesigning -v' to find your dev ID." + exit 0 + fi + identity=$(cat .cargo/.devidentity) + framework="build/patchwork/bin/libpatchwork_rust_core.macos.framework" + if [ ! -d "$framework" ]; then + exit 0 + fi + for dylib in "$framework"/*.dylib; do + [ -f "$dylib" ] && codesign -s "$identity" -f "$dylib" + done + codesign --deep -s "$identity" -f "$framework" # Build the multi-arch target for MacOS. [parallel] -_build-plugin-all-macos profile: (_build-plugin "aarch64-apple-darwin" profile) (_build-plugin "x86_64-apple-darwin" profile) _make-plugin-dir +_build-plugin-all-macos profile tracing_support: (_build-plugin "aarch64-apple-darwin" profile tracing_support) \ + (_build-plugin "x86_64-apple-darwin" profile tracing_support) _make-plugin-dir mkdir -p build/patchwork/bin # Copy the entire macos directory to get the Resources framework directory @@ -150,10 +193,11 @@ _build-plugin-all-macos profile: (_build-plugin "aarch64-apple-darwin" profile) lipo -create -output build/patchwork/bin/libpatchwork_rust_core.macos.framework/libpatchwork_rust_core.dylib \ target/aarch64-apple-darwin/{{profile}}/libpatchwork_rust_core.dylib \ target/x86_64-apple-darwin/{{profile}}/libpatchwork_rust_core.dylib - # TODO: Perhaps sign here instead of in github actions? + + just _sign-macos-plugin [parallel] -_build-plugin-single-arch architecture profile: (_build-plugin architecture profile) _make-plugin-dir +_build-plugin-single-arch architecture profile tracing_support: (_build-plugin architecture profile tracing_support) _make-plugin-dir #!/usr/bin/env sh # set -euo pipefail mkdir -p build/patchwork/bin @@ -175,6 +219,7 @@ _build-plugin-single-arch architecture profile: (_build-plugin architecture prof if [ -f "target/{{architecture}}/{{profile}}/libpatchwork_rust_core.dylib" ] ; then cp "target/{{architecture}}/{{profile}}/libpatchwork_rust_core.dylib" \ build/patchwork/bin/libpatchwork_rust_core.macos.framework/libpatchwork_rust_core.macos.dylib + just _sign-macos-plugin fi if [ -f "target/{{architecture}}/{{profile}}/patchwork_rust_core.pdb" ] ; then @@ -235,11 +280,12 @@ _configure-patchwork: _make-plugin-dir # Build the plugin and output it to the plugin build dir. For MacOS multi-arch, use architecture=all-apple-darwin to build all architectures. [parallel] [arg('profile', pattern='release|debug')] -build-patchwork profile architecture=(default_arch): _configure-patchwork _link-public +[arg('tracing_support', pattern='none|tokio-console')] +build-patchwork profile architecture=(default_arch) tracing_support="none": _configure-patchwork _link-public #!/usr/bin/env sh # set -euxo pipefail if [[ "{{architecture}}" = "all-apple-darwin" ]] ; then - just _build-plugin-all-macos "{{profile}}" + just _build-plugin-all-macos "{{profile}}" "{{tracing_support}}" exit 0 fi @@ -248,7 +294,7 @@ build-patchwork profile architecture=(default_arch): _configure-patchwork _link- profile="release_debug" fi - just _build-plugin-single-arch "{{architecture}}" "$profile" + just _build-plugin-single-arch "{{architecture}}" "$profile" "{{tracing_support}}" # Reset the Godot repository, removing the linked module and resetting the repo state. clean-godot: @@ -348,16 +394,18 @@ _write-url project url: (_link-project project) [arg('project', pattern='moddable-platformer|threadbare')] [arg('patchwork_profile', pattern='release|debug')] [arg('godot_profile', pattern='release|debug|sani')] -prepare project="moddable-platformer" patchwork_profile="release" godot_profile="release" server_url="":\ - (_link-project project) (build-godot godot_profile) (build-patchwork patchwork_profile) (_write-url project server_url) +[arg('tracing_support', pattern='none|tokio-console')] +prepare project="moddable-platformer" patchwork_profile="release" godot_profile="release" server_url="" tracing_support="none": \ + (_link-project project) (build-godot godot_profile) (build-patchwork patchwork_profile default_arch tracing_support) (_write-url project server_url) # Launch a project with Godot. Available projects are threadbare, moddable-platformer. [arg('project', pattern='moddable-platformer|threadbare')] [arg('patchwork_profile', pattern='release|debug')] [arg('godot_profile', pattern='release|debug|sani')] -launch project="moddable-platformer" patchwork_profile="release" godot_profile="release" server_url="": \ - (prepare project patchwork_profile godot_profile server_url) +[arg('tracing_support', pattern='none|tokio-console')] +launch project="moddable-platformer" patchwork_profile="release" godot_profile="release" server_url="" tracing_support="none": \ + (prepare project patchwork_profile godot_profile server_url tracing_support) #!/usr/bin/env sh # set -euxo pipefail @@ -387,11 +435,18 @@ launch project="moddable-platformer" patchwork_profile="release" godot_profile=" echo "If you think this OS should be supported, please open an issue on Github with your use-case and system details." exit 1 ;; esac - - if [[ {{godot_profile}} = "release" ]] ; then - godot_path="build/godot/bin/godot.$platform.editor.$arch$ext" + if [[ "{{os()}}" = "macos" ]] ; then + if [[ {{godot_profile}} = "release" ]] ; then + godot_path="build/godot/bin/godot_macos_editor.app/Contents/MacOS/Godot" + else + godot_path="build/godot/bin/godot_macos_editor_dev.app/Contents/MacOS/Godot" + fi else - godot_path="build/godot/bin/godot.$platform.editor.dev.$arch$ext" + if [[ {{godot_profile}} = "release" ]] ; then + godot_path="build/godot/bin/godot.$platform.editor.$arch$ext" + else + godot_path="build/godot/bin/godot.$platform.editor.dev.$arch$ext" + fi fi $godot_path -e --path "build/{{project}}" \ No newline at end of file diff --git a/public/gdscript/diff_inspector_container.gd b/public/gdscript/diff_inspector_container.gd index a34fc88d..db3e779d 100644 --- a/public/gdscript/diff_inspector_container.gd +++ b/public/gdscript/diff_inspector_container.gd @@ -87,16 +87,6 @@ func update_property_editor(editor_property) -> void: editor_property.update_property() editor_property._update_editor_property_status() -func getDeletedNodes() -> Array: - return deleted_nodes - -func getAddedNodes() -> Array: - return added_nodes - -func getChangedNodes() -> Array: - return changed_nodes - - func get_diff_stylebox(color: Color) -> StyleBoxTexture: var stylebox: StyleBoxTexture = StyleBoxTexture.new() stylebox.texture = diff_stylebox_tex @@ -541,6 +531,53 @@ func add_node_diff(file_section: DiffInspectorSection, file_path: String, node_d pop_node_sections(key, child_map[key], file_section, file_path) +func add_text_resource_diff(inspector_section: DiffInspectorSection, changed_sub_resources: Array, changed_main_resource: Dictionary) -> void: + inspector_section.get_vbox().add_child(HSeparator.new()) + if changed_main_resource is Dictionary and changed_main_resource.size() > 0: + add_sub_resource_diff(inspector_section, changed_main_resource["change_type"], changed_main_resource["sub_resource_id"], changed_main_resource["resource_type"], changed_main_resource["changed_props"]) + + for sub_resource in changed_sub_resources: + var change_type = sub_resource["change_type"] + var sub_resource_id = sub_resource["sub_resource_id"] + var sub_resource_type = sub_resource["resource_type"] + var changed_properties = sub_resource["changed_props"] + add_sub_resource_diff(inspector_section, change_type, sub_resource_id, sub_resource_type, changed_properties) + +func add_sub_resource_diff(inspector_section: DiffInspectorSection, change_type: String, sub_resource_id: String, sub_resource_type: String, changed_properties: Dictionary) -> void: + if (changed_properties.size() == 0 and change_type == "modified"): + print("!!! no prop diffs for ", sub_resource_id, " with type ", change_type) + return + var color: Color = modified_color + var subresource_label = sub_resource_id + if subresource_label.is_empty(): + subresource_label = "Main (" + sub_resource_type + ")" + if change_type == "added": + color = added_color + subresource_label += " (Added)" + elif change_type == "removed": + color = removed_color + subresource_label += " (Deleted)" + elif change_type == "modified": + color = modified_color + subresource_label += " (Modified)" + + var i = 0 + var child_section: DiffInspectorSection = DiffInspectorSection.new() + var fake_node: MissingResource = MissingResource.new() + fake_node.original_class = sub_resource_type + child_section.setup(sub_resource_id, subresource_label, fake_node, color, true, 1, 2) + child_section.set_type(change_type) + var vbox = child_section.get_vbox() + vbox.add_child(HSeparator.new()) + # get the length of the prop_diffs dictionary + for prop_name in changed_properties.keys(): + if i > 0: + var divider = HSeparator.new() + vbox.add_child(divider) + add_PropertyDiffResult(child_section, changed_properties[prop_name], sub_resource_type) + i += 1 + inspector_section.get_vbox().add_child(child_section) + func add_FileDiffResult(file_path: String, file_diff: Dictionary) -> void: @@ -580,6 +617,11 @@ func add_FileDiffResult(file_path: String, file_diff: Dictionary) -> void: var text_diff = file_diff["text_diff"] add_text_diff(inspector_section, text_diff) inspector_section.connect("box_clicked", self._on_text_box_clicked) + elif type == "text_resource_changed": + var changed_sub_resources = file_diff["changed_sub_resources"] + var changed_main_resource = file_diff["changed_main_resource"] + add_text_resource_diff(inspector_section, changed_sub_resources, changed_main_resource) + inspector_section.connect("box_clicked", self._on_resource_box_clicked) elif type == "scene_changed": var node_diffs: Array = file_diff["changed_nodes"] node_diffs.sort_custom(func(a, b): return a["node_path"] < b["node_path"]) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7c4f35df..d4a38f98 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -16,20 +16,28 @@ bench = false doc = false [features] -# default = ["automerge_0_6"] +default = [] automerge_0_6 = [] +tokio-console = ["dep:console-subscriber", "tokio/tracing"] [dependencies] automerge = "0.7.2" -autosurgeon = "0.10.1" +# path at /Users/nikita/Workspace/autosurgeon/autosurgeon +# autosurgeon = { path = "/Users/nikita/Workspace/autosurgeon/autosurgeon" } +autosurgeon = { git = "https://github.com/nikitalita/autosurgeon.git", branch = "with-tuples" } futures = "0.3.31" -godot = "0.4.3" -tokio = { version = "1.41.1", features = ["full"] } +godot = {version = "0.4.5", features = ["experimental-threads"]} +tokio = { version = "1.49.0", features = ["full"] } +console-subscriber = { version = "0.5.0", optional = true } tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] } tree-sitter = "0.25.4" tree-sitter-godot-resource = { git = "https://github.com/nikitalita/tree-sitter-godot-resource.git", branch = "fix-string-names" } safer-ffi = "0.1.13" serde_json = "1.0.138" +base64 = "0.22" +lexical = { version = "7.0.5", features = ["format"] } +num-traits = "0.2.19" +indexmap = "2.13.0" uuid = "1.5.0" pretty_assertions = "1.4.1" similar = { version = "2.4.0", features = ["unicode"] } @@ -47,7 +55,7 @@ rand = "0.9.2" # patchwork_rust_post_build_script = { path = "patchwork_rust_post_build_script" } serde = { version = "1.0", features = ["derive"] } -yet-another-md5 = "2.0.0" +md5 = "0.8.0" glob = "0.3.1" rlimit = "0.10.2" tracing = "0.1.40" @@ -57,6 +65,9 @@ samod = { version="0.7.1", features = ["tokio", "threadpool", "tungstenite"] } rayon = "1.11.0" regex = "1.12.2" tokio-tungstenite = "0.27.0" +async-stream = "0.3.6" +tokio-stream = { version = "0.1.18", features = ["sync"] } +tokio-util = "0.7.18" [build-dependencies] cbindgen = "^0.27" diff --git a/rust/src/diff/differ.rs b/rust/src/diff/differ.rs index 0c0c77a3..0fa56b48 100644 --- a/rust/src/diff/differ.rs +++ b/rust/src/diff/differ.rs @@ -1,23 +1,20 @@ use std::{ - cell::RefCell, - collections::{HashMap, HashSet}, - path::PathBuf, + cell::RefCell, collections::{HashMap, HashSet}, path::PathBuf, str::FromStr, sync::Arc }; use automerge::ChangeHash; use godot::{ - builtin::{GString, Variant}, - classes::{ResourceLoader, resource_loader::CacheMode}, - meta::ToGodot, obj::Singleton, + builtin::{GString, Variant}, classes::{ResourceLoader, resource_loader::CacheMode}, global, meta::ToGodot, obj::{EngineEnum, Singleton} }; +use tokio::sync::Mutex; use tracing::instrument; use crate::{ - diff::{resource_differ::ResourceDiff, scene_differ::SceneDiff, text_differ::TextDiff}, - fs::{file_system_driver::FileSystemEvent, file_utils::FileContent}, + diff::{resource_differ::BinaryResourceDiff, scene_differ::{SceneDiff, TextResourceDiff}, text_differ::TextDiff}, + fs::file_utils::{FileContent, FileSystemEvent}, helpers::{branch::BranchState, utils::ToShortForm}, - interop::{godot_accessors::PatchworkEditorAccessor}, - project::project::Project, + interop::godot_accessors::PatchworkEditorAccessor, + project::{branch_db::{BranchDb, HistoryRef, HistoryRefPath}, project::Project}, }; /// The type of change that occurred in a diff. @@ -36,8 +33,10 @@ pub enum ChangeType { pub enum Diff { /// A scene file diff. Scene(SceneDiff), + /// A text resource diff. + TextResourceDiff(TextResourceDiff), /// A resource file diff. - Resource(ResourceDiff), + BinaryResource(BinaryResourceDiff), /// A text file diff. Text(TextDiff), } @@ -50,219 +49,80 @@ pub struct ProjectDiff { } /// Computes diffs between two sets of heads in a project. -pub struct Differ<'a> { - /// The project we're diffing. - pub(super) project: &'a Project, - - /// The current heads we're diffing between. - pub(super) curr_heads: Vec, - - /// The previous heads we're diffing between. - pub(super) prev_heads: Vec, - - /// Cache that stores our loaded ExtResources so far. - loaded_ext_resources: RefCell>, - - // The branch we're currently diffing on - pub(super) branch_state: &'a BranchState, +#[derive(Debug)] +pub struct Differ { + /// The [BranchDb] we're working off. + branch_db: BranchDb, } -impl<'a> Differ<'a> { +impl Differ { /// Creates a new [Differ]. - pub fn new( - project: &'a Project, - curr_heads: Vec, - prev_heads: Vec, - branch_state: &'a BranchState, - ) -> Self { - let curr_heads = if curr_heads.len() == 0 { - branch_state.synced_heads.clone() - } else { - curr_heads - }; - + pub fn new(branch_db: BranchDb) -> Self { Self { - project, - curr_heads, - prev_heads, - loaded_ext_resources: RefCell::new(HashMap::new()), - branch_state, - } - } - - /// Saves and imports a temp resource at a given path for the specified heads. - fn get_resource_at( - &self, - path: &String, - file_content: &FileContent, - heads: &Vec, - ) -> Option { - let import_path = format!("{}.import", path); - let mut import_file_content = self.get_file_at(&import_path, Some(heads)); - if import_file_content.is_none() { - // try at current heads - import_file_content = self.get_file_at(&import_path, None); - } - return self.create_temp_resource_from_content( - &path, - &file_content, - &heads, - import_file_content.as_ref(), - ); - } - - /// Creates a temporary resource from file content at a given path. - pub(super) fn create_temp_resource_from_content( - &self, - path: &str, - content: &FileContent, - heads: &Vec, - import_file_content: Option<&FileContent>, - ) -> Option { - let temp_dir = format!("res://.patchwork/temp_{}/", heads.first().to_short_form()); - let temp_path = path.replace("res://", &temp_dir); - if let Err(e) = FileContent::write_file_content( - &PathBuf::from(self.project.globalize_path(&temp_path)), - content, - ) { - tracing::error!("error writing file to temp path: {:?}", e); - return None; - } - - if let Some(import_file_content) = import_file_content { - if let FileContent::String(import_file_content) = import_file_content { - let import_file_content = import_file_content.replace("res://", &temp_dir); - // regex to replace uid=uid://<...> and uid=uid:// with uid=uid://<...> and uid=uid:// - let import_file_content = - import_file_content.replace(r#"uid=uid://[^\n]+"#, "uid=uid://"); - // write the import file content to the temp path - let import_file_path: String = format!("{}.import", temp_path); - let _ = FileContent::write_file_content( - &PathBuf::from(self.project.globalize_path(&import_file_path)), - &FileContent::String(import_file_content), - ); - - let res = PatchworkEditorAccessor::import_and_load_resource(&temp_path); - if res.is_nil() { - tracing::error!("error importing resource: {:?}", temp_path); - return None; - } - tracing::debug!("successfully imported resource: {:?}", temp_path); - return Some(res); - } - } - let resource = ResourceLoader::singleton() - .load_ex(&GString::from(&temp_path)) - .cache_mode(CacheMode::IGNORE_DEEP) - .done(); - if let Some(resource) = resource { - return Some(resource.to_variant()); - } - None - } - - /// Gets the file content at a given path for the specified heads. - pub(super) fn get_file_at( - &self, - path: &String, - heads: Option<&Vec>, - ) -> Option { - let mut ret: Option = None; - { - let files = self - .project - .get_files_at(heads, Some(&HashSet::from_iter(vec![path.clone()]))); - for file in files.into_iter() { - if file.0 == *path { - ret = Some(file.1); - break; - } else { - panic!( - "Returned a file that didn't match the path!?!??!?!?!?!?!?!!? {:?} != {:?}", - file.0, path - ); - } - } + branch_db, } - ret } /// Loads an ExtResource given a path, using a cache. - pub(super) fn load_ext_resource( + pub(super) async fn start_load_ext_resource( &self, path: &String, - heads: &Vec, - ) -> Option { - if let Some(resource) = self.loaded_ext_resources.borrow().get(path) { - return Some(resource.clone()); - } - - let resource_content = self.get_file_at(path, Some(heads)); - let Some(resource_content) = resource_content else { - return None; - }; + ref_: &HistoryRef + ) -> Result { + let history_ref_path = HistoryRefPath::make_path_string(ref_, path).map_err(|_| "Invalid history ref path".to_string())?; - let Some(resource) = self.get_resource_at(path, &resource_content, heads) else { - return None; + return match ResourceLoader::singleton().load_threaded_request(&history_ref_path) { + global::Error::OK => Ok(history_ref_path), + e => Err(format!("load_threaded_request failed ({})", e.as_str().to_string())), }; - - self.loaded_ext_resources - .borrow_mut() - .insert(path.clone(), resource.clone()); - Some(resource) } /// Computes the diff between the two sets of heads. #[instrument(skip_all, level = tracing::Level::DEBUG)] - pub fn get_diff(&self) -> ProjectDiff { - tracing::debug!( - "branch {:?}, getting changes between {} and {}", - self.branch_state.name, - self.prev_heads.to_short_form(), - self.curr_heads.to_short_form() - ); - - if self.prev_heads == self.curr_heads { + pub async fn get_diff(&self, before: &HistoryRef, after: &HistoryRef) -> ProjectDiff { + if before == after { tracing::debug!("no changes"); return ProjectDiff::default(); } - let mut diffs: Vec = vec![]; - // Get old and new content - let new_file_contents = self.project.get_changed_file_content_between( - None, - self.branch_state.doc_handle.document_id().clone(), - self.prev_heads.clone(), - self.curr_heads.clone(), - false, - ); - let changed_files_set: HashSet = new_file_contents + // TODO: refactor `get_changed_file_content_between_refs` to not globalize the paths so we don't have to re-localize them here + // Get the set of new file content that has changed + let Some(new_file_contents) = self + .branch_db + .get_changed_file_content_between_refs(Some(before), after, false) + .await + .and_then(|events| Some(events.into_iter().map(|event| { + match event { + FileSystemEvent::FileCreated(path, content) => (self.branch_db.localize_path(&path), content, ChangeType::Added), + FileSystemEvent::FileModified(path, content) => (self.branch_db.localize_path(&path), content, ChangeType::Modified), + FileSystemEvent::FileDeleted(path) => (self.branch_db.localize_path(&path), FileContent::Deleted, ChangeType::Removed), + } + }).collect::>())) + else { + // Something went wrong + return ProjectDiff::default(); + }; + + let changed_filter: HashSet = new_file_contents .iter() - .map(|event| match event { - FileSystemEvent::FileCreated(path, _) => path.to_string_lossy().to_string(), - FileSystemEvent::FileModified(path, _) => path.to_string_lossy().to_string(), - FileSystemEvent::FileDeleted(path) => path.to_string_lossy().to_string(), - }) + .map(|event| event.0.clone()) .collect::>(); - let old_file_contents = self.project.get_files_on_branch_at( - self.branch_state, - Some(&self.prev_heads), - Some(&changed_files_set), - ); - for event in &new_file_contents { - let (path, new_file_content, change_type) = match event { - FileSystemEvent::FileCreated(path, content) => (path, content, ChangeType::Added), - FileSystemEvent::FileModified(path, content) => { - (path, content, ChangeType::Modified) - } - FileSystemEvent::FileDeleted(path) => { - (path, &FileContent::Deleted, ChangeType::Removed) - } - }; - let path = path.to_string_lossy().to_string(); + // We do need to compare the new files to the old files, so grab the old contents with a filter + let Some(old_file_contents) = self + .branch_db + .get_files_at_ref(before, &changed_filter) + .await + else { + // Something went wrong + return ProjectDiff::default(); + }; + + let mut diffs: Vec = vec![]; + + for (path, new_file_content, change_type) in &new_file_contents { let old_file_content = old_file_contents - .get(&path) + .get(path) .unwrap_or(&FileContent::Deleted); if matches!(old_file_content, FileContent::Scene(_)) @@ -276,27 +136,47 @@ impl<'a> Differ<'a> { FileContent::Scene(s) => Some(s), _ => None, }; - // This is a scene file, so use a scene diff - diffs.push(Diff::Scene( - self.get_scene_diff(&path, old_scene, new_scene), - )); + + + let resource_type = match (old_scene, new_scene) { + (None, Some(scene)) => scene.resource_type.clone(), + (Some(scene), None) => scene.resource_type.clone(), + (_, Some(scene)) => scene.resource_type.clone(), + (_, _) => "".to_string(), + }; + if resource_type == "PackedScene" { + diffs.push(Diff::Scene( + self.get_scene_diff(&path, old_scene, new_scene, before, after) + .await, + )); + } else { + diffs.push(Diff::TextResourceDiff( + self.get_text_resource_diff(&path, old_scene, new_scene, before, after) + .await, + )); + } } else if matches!(old_file_content, FileContent::Binary(_)) || matches!(new_file_content, FileContent::Binary(_)) { // This is a binary file, so use a resource diff - diffs.push(Diff::Resource(self.get_resource_diff( - &path, - change_type, - old_file_content, - new_file_content, - ))); + diffs.push(Diff::BinaryResource( + self.get_binary_resource_diff( + &path, + change_type.clone(), + old_file_content, + new_file_content, + before, + after, + ) + .await, + )); } else if matches!(old_file_content, FileContent::String(_)) || matches!(new_file_content, FileContent::String(_)) { // This is a text file, so do a text diff. diffs.push(Diff::Text(self.get_text_diff( &path, - change_type, + change_type.clone(), old_file_content, new_file_content, ))); diff --git a/rust/src/diff/resource_differ.rs b/rust/src/diff/resource_differ.rs index dc2e26a3..edf5ff9e 100644 --- a/rust/src/diff/resource_differ.rs +++ b/rust/src/diff/resource_differ.rs @@ -1,27 +1,26 @@ -use automerge::ChangeHash; use godot::builtin::Variant; use crate::{ - diff::differ::{ChangeType, Differ}, - fs::file_utils::FileContent, + diff::{differ::{ChangeType, Differ}, scene_differ::DiffVariantValue}, fs::file_utils::FileContent, helpers::utils::ToShortForm, parser::parser_defs::VariantVal, project::branch_db::HistoryRef }; + #[derive(Clone, Debug)] -pub struct ResourceDiff { +pub struct BinaryResourceDiff { pub path: String, pub change_type: ChangeType, - pub old_resource: Option, - pub new_resource: Option, + pub old_resource: Option, + pub new_resource: Option, } -impl ResourceDiff { +impl BinaryResourceDiff { pub fn new( path: String, change_type: ChangeType, - old_resource: Option, - new_resource: Option, - ) -> ResourceDiff { - ResourceDiff { + old_resource: Option, + new_resource: Option, + ) -> BinaryResourceDiff { + BinaryResourceDiff { path, change_type, old_resource, @@ -30,42 +29,38 @@ impl ResourceDiff { } } -impl Differ<'_> { - pub(super) fn get_resource_diff( +impl Differ { + pub(super) async fn get_binary_resource_diff( &self, path: &String, change_type: ChangeType, old_content: &FileContent, new_content: &FileContent, - ) -> ResourceDiff { - ResourceDiff::new( + before: &HistoryRef, + after: &HistoryRef + ) -> BinaryResourceDiff { + BinaryResourceDiff::new( path.clone(), change_type, - self.get_resource(path, old_content, &self.prev_heads), - self.get_resource(path, new_content, &self.curr_heads), + self.get_resource(path, old_content, before).await, + self.get_resource(path, new_content, after).await, ) } - fn get_resource( + async fn get_resource( &self, path: &String, - content: &FileContent, - heads: &Vec, - ) -> Option { - let import_path = format!("{}.import", path); - let import_file_content = match content { - FileContent::Deleted => None, - _ => self - .get_file_at(&import_path, Some(heads)) - // try at current heads - .or(self.get_file_at(&import_path, None)), - }; + _content: &FileContent, + ref_: &HistoryRef, + ) -> Option { + if matches!(_content, FileContent::Deleted) { + return None; + } + + match self.start_load_ext_resource(&path, ref_).await{ + Ok(load_path) => Some(DiffVariantValue::LazyLoadData(path.clone(), load_path)), + Err(e) => Some(DiffVariantValue::Variant(VariantVal::String(format!("\"\"", path, e)))), + } - self.create_temp_resource_from_content( - &path, - content, - &self.prev_heads, - import_file_content.as_ref(), - ) } } diff --git a/rust/src/diff/scene_differ.rs b/rust/src/diff/scene_differ.rs index 3f8584da..d6d88bc5 100644 --- a/rust/src/diff/scene_differ.rs +++ b/rust/src/diff/scene_differ.rs @@ -1,17 +1,11 @@ use std::collections::{HashMap, HashSet}; -use godot::{ - builtin::{StringName, Variant}, - classes::{ClassDb}, - global::str_to_var, - meta::ToGodot, obj::Singleton, -}; - use crate::{ diff::differ::{ChangeType, Differ}, - parser::godot_parser::{ - ExternalResourceNode, GodotNode, GodotScene, SubResourceNode, TypeOrInstance, - }, + parser::{godot_parser::{ + ExternalResourceNode, GodotNode, GodotScene, SubResourceNode, TypeOrInstance + }, parser_defs::{OrderedProperty, VariantVal}}, + project::branch_db::HistoryRef, }; /// Represents a diff of a scene, with a scene path and a list of changed nodes. @@ -35,6 +29,31 @@ impl SceneDiff { } } +#[derive(Clone, Debug)] +pub struct TextResourceDiff { + /// The path of the scene. + pub path: String, + /// PackedScene or other + pub resource_type: String, + /// The change type for the scene. + pub change_type: ChangeType, + /// The sub resources changed in this diff. + pub changed_sub_resources: Vec, + pub changed_main_resource: Option, +} + +impl TextResourceDiff { + fn new(path: String, resource_type: String, change_type: ChangeType, changed_sub_resources: Vec, changed_main_resource: Option) -> TextResourceDiff { + TextResourceDiff { + path, + resource_type, + change_type, + changed_sub_resources, + changed_main_resource, + } + } +} + /// Represents a diff of a single node within a scene, with a collection of changed properties. #[derive(Clone, Debug)] pub struct NodeDiff { @@ -64,6 +83,31 @@ impl NodeDiff { } } + + +#[derive(Clone, Debug)] +pub struct SubResourceDiff { + pub change_type: ChangeType, + pub sub_resource_id: String, + pub resource_type: String, + pub changed_properties: HashMap, +} + +impl SubResourceDiff { + pub fn new( + change_type: ChangeType, + sub_resource_id: String, + resource_type: String, + changed_properties: HashMap, + ) -> SubResourceDiff { + SubResourceDiff { + change_type, + sub_resource_id, + resource_type, + changed_properties, + } + } +} /// Represents a diff of a single Property within a Node, within a Scene. #[derive(Clone, Debug)] pub struct PropertyDiff { @@ -72,17 +116,17 @@ pub struct PropertyDiff { /// The change type of the property. pub change_type: ChangeType, /// The old value of the property, if it existed. - pub old_value: Option, + pub old_value: Option, /// The new value of the property, if it still exists. - pub new_value: Option, + pub new_value: Option, } impl PropertyDiff { pub fn new( name: String, change_type: ChangeType, - old_value: Option, - new_value: Option, + old_value: Option, + new_value: Option, ) -> Self { PropertyDiff { name, @@ -93,39 +137,71 @@ impl PropertyDiff { } } -/// The different types of Godot-recognized string values that can be stored in a Variant. -#[derive(PartialEq, Debug)] -enum VariantStrValue { - /// A normal string that doesn't refer to a resource. - Variant(String), - /// A Godot resource path string. - ResourcePath(String), - /// A Godot sub-resource identifier string. - SubResourceID(String), - /// A Godot external resource identifier string. - ExtResourceID(String), + +#[derive(Clone, Debug)] +pub enum DiffVariantValue { + /// A normal variant string + Variant(VariantVal), + /// Type/instance name, property name + DefaultValue(TypeOrInstance, String), + /// original path, load path + LazyLoadData(String, String), } -/// Implement the to_string method for this enum -impl std::fmt::Display for VariantStrValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - VariantStrValue::Variant(s) => write!(f, "{}", s), - VariantStrValue::ResourcePath(s) => write!(f, "Resource({})", s), - VariantStrValue::SubResourceID(s) => write!(f, "SubResource({})", s), - VariantStrValue::ExtResourceID(s) => write!(f, "ExtResource({})", s), - } +trait PropertyGetter { + fn get_property(&self, prop: &String) -> Option<&OrderedProperty>; + fn get_properties(&self) -> &HashMap; + fn get_type_or_instance(&self) -> TypeOrInstance; + fn is_subresource(&self) -> bool; + fn get_id(&self) -> String; +} + +impl PropertyGetter for GodotNode { + fn get_property(&self, prop: &String) -> Option<&OrderedProperty> { + self.properties.get(prop) + } + fn get_properties(&self) -> &HashMap { + &self.properties + } + fn get_type_or_instance(&self) -> TypeOrInstance { + self.type_or_instance.clone() + } + fn is_subresource(&self) -> bool { + false + } + fn get_id(&self) -> String { + self.id.to_string() + } +} + +impl PropertyGetter for SubResourceNode { + fn get_property(&self, prop: &String) -> Option<&OrderedProperty> { + self.properties.get(prop) + } + fn get_properties(&self) -> &HashMap { + &self.properties + } + fn get_type_or_instance(&self) -> TypeOrInstance { + TypeOrInstance::Type(self.resource_type.clone()) + } + fn is_subresource(&self) -> bool { + true + } + fn get_id(&self) -> String { + self.id.to_string() } } /// Implement scene-related functions on the Differ -impl Differ<'_> { +impl Differ { /// Generate a [SceneDiff] between the previous and current heads. - pub(super) fn get_scene_diff( + pub(super) async fn get_scene_diff( &self, path: &String, old_scene: Option<&GodotScene>, new_scene: Option<&GodotScene>, + before: &HistoryRef, + after: &HistoryRef, ) -> SceneDiff { let mut node_ids = HashSet::new(); let mut sub_resource_ids = HashSet::new(); @@ -156,7 +232,7 @@ impl Differ<'_> { let old_node = old_scene.as_ref().and_then(|s| s.get_node(*node_id)); let new_node = new_scene.as_ref().and_then(|s| s.get_node(*node_id)); - let Some(diff) = self.get_node_diff(*node_id, old_node, new_node, old_scene, new_scene) + let Some(diff) = self.get_node_diff(*node_id, old_node, new_node, old_scene, new_scene, before, after).await else { // If the node has no changes or is otherwise invalid, just skip this one. continue; @@ -176,33 +252,147 @@ impl Differ<'_> { ) } + pub(super) async fn get_text_resource_diff( + &self, + path: &String, + old_scene: Option<&GodotScene>, + new_scene: Option<&GodotScene>, + before: &HistoryRef, + after: &HistoryRef, + ) -> TextResourceDiff { + let mut node_ids = HashSet::new(); + let mut sub_resource_ids = HashSet::new(); + let mut ext_resource_ids = HashSet::new(); + + let mut resource_type: String = "".to_string(); + // Collect all the relevant node IDs, sub resource IDs, and ext resource IDs from both scenes. + if let Some(old_scene) = old_scene { + resource_type = old_scene.resource_type.clone(); + Self::get_ids_from_scene( + old_scene, + &mut node_ids, + &mut ext_resource_ids, + &mut sub_resource_ids, + ); + } + if let Some(new_scene) = new_scene { + resource_type = new_scene.resource_type.clone(); + Self::get_ids_from_scene( + new_scene, + &mut node_ids, + &mut ext_resource_ids, + &mut sub_resource_ids, + ); + } + + let mut changed_sub_resources = Vec::new(); + // Diff each node + for sub_resource_id in &sub_resource_ids { + let old_sub_resource = old_scene.as_ref().and_then(|s| s.sub_resources.get(sub_resource_id)); + let new_sub_resource = new_scene.as_ref().and_then(|s| s.sub_resources.get(sub_resource_id)); + + let Some(diff) = self.get_sub_resource_diff(sub_resource_id, old_sub_resource, new_sub_resource, old_scene, new_scene, before, after).await + else { + // If the node has no changes or is otherwise invalid, just skip this one. + continue; + }; + + changed_sub_resources.push(diff); + } + let changed_main_resource = self.get_sub_resource_diff(&"".to_string(), old_scene.and_then(|s| s.main_resource.as_ref()), new_scene.and_then(|s| s.main_resource.as_ref()), old_scene, new_scene, before, after).await; + + TextResourceDiff::new( + path.clone(), + resource_type, + match (old_scene, new_scene) { + (None, Some(_)) => ChangeType::Added, + (Some(_), None) => ChangeType::Removed, + (_, _) => ChangeType::Modified, + }, + changed_sub_resources, + changed_main_resource, + ) + + } + + async fn get_sub_resource_diff( + &self, + sub_resource_id: &String, + old_node: Option<&SubResourceNode>, + new_node: Option<&SubResourceNode>, + old_scene: Option<&GodotScene>, + new_scene: Option<&GodotScene>, + before: &HistoryRef, + after: &HistoryRef, + ) -> Option { + if old_node.is_none() && new_node.is_none() { + return None; + } + + let mut changed_properties = HashMap::new(); + let old_class_name = old_node.map(|n| n.get_type_or_instance().to_string()); + let new_class_name = new_node.map(|n| n.get_type_or_instance().to_string()); + + // Collect all properties from new and old scenes + let mut props: HashSet = HashSet::new(); + if let Some(node) = old_node { + for (key, _) in node.get_properties() { + let _ = props.insert(key.to_string()); + } + } + if let Some(node) = new_node { + for (key, _) in node.get_properties() { + let _ = props.insert(key.to_string()); + } + } + for prop in &props { + if let Some(prop_diff) = + self.get_property_diff(prop, old_node, new_node, old_scene, new_scene, before, after).await + { + changed_properties.insert(prop.clone(), prop_diff); + } + } + Some(SubResourceDiff::new( + match (old_node, new_node) { + (None, Some(_)) => ChangeType::Added, + (Some(_), None) => ChangeType::Removed, + (_, _) => ChangeType::Modified, + }, + sub_resource_id.clone(), + old_class_name.or(new_class_name)?, + changed_properties, + )) + } + /// Generate a [NodeDiff] between two nodes. - fn get_node_diff( + async fn get_node_diff( &self, node_id: i32, - old_node: Option<&GodotNode>, - new_node: Option<&GodotNode>, + old_node: Option<&impl PropertyGetter>, + new_node: Option<&impl PropertyGetter>, old_scene: Option<&GodotScene>, new_scene: Option<&GodotScene>, + before: &HistoryRef, + after: &HistoryRef, ) -> Option { if old_node.is_none() && new_node.is_none() { return None; } - let old_class_name = old_node.map(|n| Self::get_class_name(&n.type_or_instance, old_scene)); - let new_class_name = new_node.map(|n| Self::get_class_name(&n.type_or_instance, new_scene)); + let old_class_name = old_node.map(|n| Self::get_class_name(&n.get_type_or_instance(), old_scene)); + let new_class_name = new_node.map(|n| Self::get_class_name(&n.get_type_or_instance(), new_scene)); let mut changed_properties = HashMap::new(); // Collect all properties from new and old scenes let mut props: HashSet = HashSet::new(); if let Some(node) = old_node { - for (key, _) in &node.properties { + for (key, _) in node.get_properties() { let _ = props.insert(key.to_string()); } } if let Some(node) = new_node { - for (key, _) in &node.properties { + for (key, _) in node.get_properties() { let _ = props.insert(key.to_string()); } } @@ -210,7 +400,7 @@ impl Differ<'_> { // Iterate through the props for prop in &props { if let Some(prop_diff) = - self.get_property_diff(prop, old_node, new_node, old_scene, new_scene) + self.get_property_diff(prop, old_node, new_node, old_scene, new_scene, before, after).await { changed_properties.insert(prop.clone(), prop_diff); } @@ -261,66 +451,59 @@ impl Differ<'_> { } } - /// Returns the [VariantStrValue] of a property on a node, or the default value if the property doesn't - /// exist on the node. - /// If the node itself doesn't exist, returns [None]. - fn get_varstr_or_default(prop: &String, node: Option<&GodotNode>) -> Option { + + /// Returns the [VariantVal] of a property on a node, or None if the node doesn't exist or the property doesn't exist on the node. + fn get_variant_or_none<'a>(prop: &'a String, node: Option<&'a impl PropertyGetter>) -> Option<&'a VariantVal> { // If this node never existed, don't provide a value. let Some(node) = node else { return None; }; - - let val = node.properties.get(prop).map_or_else( - || - // If the property doesn't exist on the node, calculate the default. - match &node.type_or_instance { - TypeOrInstance::Type(class_name) => ClassDb::singleton() - .class_get_property_default_value( - &StringName::from(class_name), - &StringName::from(prop), - ) - .to_string(), - // Instance properties are always set, regardless of the default value, so this is always empty - _ => "".to_string(), - }, - // Otherwise, get the value from the property. - |val| val.get_value(), - ); - - Some(Self::get_varstr_value(val)) + match node.get_property(prop) { + Some(val) => Some(&val.value), + None => None, + } } /// Returns a [PropertyDiff] comparing the old property value versus the new one. /// Returns [None] if neither node is valid, or if the value has not meaningfully changed. - fn get_property_diff( + async fn get_property_diff( &self, prop: &String, - old_node: Option<&GodotNode>, - new_node: Option<&GodotNode>, + old_node: Option<&impl PropertyGetter>, + new_node: Option<&impl PropertyGetter>, old_scene: Option<&GodotScene>, new_scene: Option<&GodotScene>, + before: &HistoryRef, + after: &HistoryRef, ) -> Option { // If neither node is valid, there's no valid property diff. if new_node.is_none() && old_node.is_none() { return None; }; - // Slightly weird hack: Diff against the default instead of the normal property. - let old_value = Self::get_varstr_or_default(prop, old_node); - let new_value = Self::get_varstr_or_default(prop, new_node); + let old_value = Self::get_variant_or_none(prop, old_node); + let new_value = Self::get_variant_or_none(prop, new_node); // Skip in case of no changes - if !self.did_prop_change(old_value.as_ref(), new_value.as_ref(), old_scene, new_scene) { + if !self.did_prop_change(old_value, new_value, old_scene, new_scene) { return None; } // Expensive: Load any ext resources and turn them into Variants - let old = old_value - .as_ref() - .map(|v| self.get_prop_value(&v, old_scene, true, prop == "script")); - let new = new_value - .as_ref() - .map(|v| self.get_prop_value(&v, new_scene, false, prop == "script")); + let old = match &old_value { + Some(v) => Some(self.get_prop_value(v, old_scene, true, prop == "script", before, after).await), + None => match old_node { + Some(node) => Some(DiffVariantValue::DefaultValue(node.get_type_or_instance(), prop.clone())), + None => None, + }, + }; + let new = match &new_value { + Some(v) => Some(self.get_prop_value(v, new_scene, false, prop == "script", before, after).await), + None => match new_node { + Some(node) => Some(DiffVariantValue::DefaultValue(node.get_type_or_instance(), prop.clone())), + None => None, + }, + }; return Some(PropertyDiff::new( prop.clone(), @@ -369,11 +552,9 @@ impl Differ<'_> { return false; }; - let old_prop = Self::get_varstr_value(old_prop.get_value()); - let new_prop = Self::get_varstr_value(new_prop.get_value()); if self.did_prop_change( - Some(&old_prop), - Some(&new_prop), + Some(&old_prop.value), + Some(&new_prop.value), Some(old_scene), Some(new_scene), ) { @@ -406,8 +587,8 @@ impl Differ<'_> { /// Check to see if a property has changed, including deep lookups of subresources and shallow lookup of extresources. fn did_prop_change( &self, - old_value: Option<&VariantStrValue>, - new_value: Option<&VariantStrValue>, + old_value: Option<&VariantVal>, + new_value: Option<&VariantVal>, old_scene: Option<&GodotScene>, new_scene: Option<&GodotScene>, ) -> bool { @@ -429,8 +610,8 @@ impl Differ<'_> { match (old_value, new_value) { // Deeply lookup subresources ( - VariantStrValue::SubResourceID(old_value), - VariantStrValue::SubResourceID(new_value), + VariantVal::SubResource(old_value), + VariantVal::SubResource(new_value), ) => self.did_sub_resource_change( old_scene.sub_resources.get(old_value), new_scene.sub_resources.get(new_value), @@ -439,52 +620,48 @@ impl Differ<'_> { ), // Shallowly lookup extresource references ( - VariantStrValue::ExtResourceID(old_value), - VariantStrValue::ExtResourceID(new_value), + VariantVal::ExtResource(old_value), + VariantVal::ExtResource(new_value), ) => self.did_ext_resource_reference_change( old_scene.ext_resources.get(old_value), new_scene.ext_resources.get(new_value), ), - // No special lookup needed for regular Variants (definitely) or ResourcePaths (I think?) + // No special lookup needed for regular Variants (definitely) or ResourcePaths ( - VariantStrValue::ResourcePath(old_value), - VariantStrValue::ResourcePath(new_value), - ) => old_value != new_value, - (VariantStrValue::Variant(old_value), VariantStrValue::Variant(new_value)) => { - old_value != new_value - } - // If the types are different, we've for sure changed - _ => true, + VariantVal::Resource(old_uid, old_path), + VariantVal::Resource(new_uid, new_path), + ) => old_uid != new_uid || old_path != new_path, + (default_a, default_b) => default_a != default_b, } } /// Returns the value of a given prop, within a given scene. /// Normally, it's a String. If it's a (non-script) ExtResource or ResourcePath, /// it loads and returns the resource content as a Variant. - fn get_prop_value( + async fn get_prop_value( &self, - prop_value: &VariantStrValue, + prop_value: &VariantVal, scene: Option<&GodotScene>, is_old: bool, is_script: bool, - ) -> Variant { + before: &HistoryRef, + after: &HistoryRef, + ) -> DiffVariantValue { // Prevent loading script files during the diff and creating issues for the editor if is_script { - return "