diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a124c80 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: "-D warnings" + +jobs: + check: + name: Check (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - uses: Swatinem/rust-cache@v2 + + - name: Check compilation + run: cargo check --workspace --all-targets + + - name: Check formatting + run: cargo fmt --all --check + + - name: Clippy + run: cargo clippy --workspace --all-targets -- -D warnings + + test: + name: Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + needs: check + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - name: Run tests + run: cargo test --workspace + + audit: + name: Security Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-audit + run: cargo install cargo-audit --locked + + - name: Audit dependencies + run: cargo audit --deny warnings diff --git a/.github/workflows/release-old.yml b/.github/workflows/release-old.yml new file mode 100644 index 0000000..d9aa406 --- /dev/null +++ b/.github/workflows/release-old.yml @@ -0,0 +1,296 @@ +# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist +# +# Copyright 2022-2024, axodotdev +# SPDX-License-Identifier: MIT or Apache-2.0 +# +# CI that: +# +# * checks for a Git Tag that looks like a release +# * builds artifacts with dist (archives, installers, hashes) +# * uploads those artifacts to temporary workflow zip +# * on success, uploads the artifacts to a GitHub Release +# +# Note that the GitHub Release will be created with a generated +# title/body based on your changelogs. + +name: Release +permissions: + "contents": "write" + +# This task will run whenever you push a git tag that looks like a version +# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. +# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where +# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION +# must be a Cargo-style SemVer Version (must have at least major.minor.patch). +# +# If PACKAGE_NAME is specified, then the announcement will be for that +# package (erroring out if it doesn't have the given version or isn't dist-able). +# +# If PACKAGE_NAME isn't specified, then the announcement will be for all +# (dist-able) packages in the workspace with that version (this mode is +# intended for workspaces with only one dist-able package, or with all dist-able +# packages versioned/released in lockstep). +# +# If you push multiple tags at once, separate instances of this workflow will +# spin up, creating an independent announcement for each one. However, GitHub +# will hard limit this to 3 tags per commit, as it will assume more tags is a +# mistake. +# +# If there's a prerelease-style suffix to the version, then the release(s) +# will be marked as a prerelease. +on: + pull_request: + push: + tags: + - '**[0-9]+.[0-9]+.[0-9]+*' + +jobs: + # Run 'dist plan' (or host) to determine what tasks we need to do + plan: + runs-on: "ubuntu-22.04" + outputs: + val: ${{ steps.plan.outputs.manifest }} + tag: ${{ !github.event.pull_request && github.ref_name || '' }} + tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} + publishing: ${{ !github.event.pull_request }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install dist + # we specify bash to get pipefail; it guards against the `curl` command + # failing. otherwise `sh` won't catch that `curl` returned non-0 + shell: bash + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh" + - name: Cache dist + uses: actions/upload-artifact@v6 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/dist + # sure would be cool if github gave us proper conditionals... + # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible + # functionality based on whether this is a pull_request, and whether it's from a fork. + # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* + # but also really annoying to build CI around when it needs secrets to work right.) + - id: plan + run: | + dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json + echo "dist ran successfully" + cat plan-dist-manifest.json + echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v6 + with: + name: artifacts-plan-dist-manifest + path: plan-dist-manifest.json + + # Build and packages all the platform-specific things + build-local-artifacts: + name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) + # Let the initial task tell us to not run (currently very blunt) + needs: + - plan + if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} + strategy: + fail-fast: false + # Target platforms/runners are computed by dist in create-release. + # Each member of the matrix has the following arguments: + # + # - runner: the github runner + # - dist-args: cli flags to pass to dist + # - install-dist: expression to run to install dist on the runner + # + # Typically there will be: + # - 1 "global" task that builds universal installers + # - N "local" tasks that build each platform's binaries and platform-specific installers + matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} + runs-on: ${{ matrix.runner }} + container: ${{ matrix.container && matrix.container.image || null }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json + steps: + - name: enable windows longpaths + run: | + git config --global core.longpaths true + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install Rust non-interactively if not already installed + if: ${{ matrix.container }} + run: | + if ! command -v cargo > /dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + fi + - name: Install dist + run: ${{ matrix.install_dist.run }} + # Get the dist-manifest + - name: Fetch local artifacts + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - name: Install dependencies + run: | + ${{ matrix.packages_install }} + - name: Build artifacts + run: | + # Actually do builds and make zips and whatnot + dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json + echo "dist ran successfully" + - id: cargo-dist + name: Post-build + # We force bash here just because github makes it really hard to get values up + # to "real" actions without writing to env-vars, and writing to env-vars has + # inconsistent syntax between shell and powershell. + shell: bash + run: | + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v6 + with: + name: artifacts-build-local-${{ join(matrix.targets, '_') }} + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + + # Build and package all the platform-agnostic(ish) things + build-global-artifacts: + needs: + - plan + - build-local-artifacts + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install cached dist + uses: actions/download-artifact@v7 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/dist + # Get all the local artifacts for the global tasks to use (for e.g. checksums) + - name: Fetch local artifacts + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: cargo-dist + shell: bash + run: | + dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json + echo "dist ran successfully" + + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v6 + with: + name: artifacts-build-global + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + # Determines if we should publish/announce + host: + needs: + - plan + - build-local-artifacts + - build-global-artifacts + # Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine) + if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: "ubuntu-22.04" + outputs: + val: ${{ steps.host.outputs.manifest }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install cached dist + uses: actions/download-artifact@v7 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/dist + # Fetch artifacts from scratch-storage + - name: Fetch artifacts + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: host + shell: bash + run: | + dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json + echo "artifacts uploaded and released successfully" + cat dist-manifest.json + echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v6 + with: + # Overwrite the previous copy + name: artifacts-dist-manifest + path: dist-manifest.json + # Create a GitHub Release while uploading all files to it + - name: "Download GitHub Artifacts" + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: artifacts + merge-multiple: true + - name: Cleanup + run: | + # Remove the granular manifests + rm -f artifacts/*-dist-manifest.json + - name: Create GitHub Release + env: + PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" + ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" + ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" + RELEASE_COMMIT: "${{ github.sha }}" + run: | + # Write and read notes from a file to avoid quoting breaking things + echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt + + gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* + + announce: + needs: + - plan + - host + # use "always() && ..." to allow us to wait for all publish jobs while + # still allowing individual publish jobs to skip themselves (for prereleases). + # "host" however must run to completion, no skipping allowed! + if: ${{ always() && needs.host.result == 'success' }} + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d9aa406 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,296 @@ +# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist +# +# Copyright 2022-2024, axodotdev +# SPDX-License-Identifier: MIT or Apache-2.0 +# +# CI that: +# +# * checks for a Git Tag that looks like a release +# * builds artifacts with dist (archives, installers, hashes) +# * uploads those artifacts to temporary workflow zip +# * on success, uploads the artifacts to a GitHub Release +# +# Note that the GitHub Release will be created with a generated +# title/body based on your changelogs. + +name: Release +permissions: + "contents": "write" + +# This task will run whenever you push a git tag that looks like a version +# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. +# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where +# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION +# must be a Cargo-style SemVer Version (must have at least major.minor.patch). +# +# If PACKAGE_NAME is specified, then the announcement will be for that +# package (erroring out if it doesn't have the given version or isn't dist-able). +# +# If PACKAGE_NAME isn't specified, then the announcement will be for all +# (dist-able) packages in the workspace with that version (this mode is +# intended for workspaces with only one dist-able package, or with all dist-able +# packages versioned/released in lockstep). +# +# If you push multiple tags at once, separate instances of this workflow will +# spin up, creating an independent announcement for each one. However, GitHub +# will hard limit this to 3 tags per commit, as it will assume more tags is a +# mistake. +# +# If there's a prerelease-style suffix to the version, then the release(s) +# will be marked as a prerelease. +on: + pull_request: + push: + tags: + - '**[0-9]+.[0-9]+.[0-9]+*' + +jobs: + # Run 'dist plan' (or host) to determine what tasks we need to do + plan: + runs-on: "ubuntu-22.04" + outputs: + val: ${{ steps.plan.outputs.manifest }} + tag: ${{ !github.event.pull_request && github.ref_name || '' }} + tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} + publishing: ${{ !github.event.pull_request }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install dist + # we specify bash to get pipefail; it guards against the `curl` command + # failing. otherwise `sh` won't catch that `curl` returned non-0 + shell: bash + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh" + - name: Cache dist + uses: actions/upload-artifact@v6 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/dist + # sure would be cool if github gave us proper conditionals... + # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible + # functionality based on whether this is a pull_request, and whether it's from a fork. + # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* + # but also really annoying to build CI around when it needs secrets to work right.) + - id: plan + run: | + dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json + echo "dist ran successfully" + cat plan-dist-manifest.json + echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v6 + with: + name: artifacts-plan-dist-manifest + path: plan-dist-manifest.json + + # Build and packages all the platform-specific things + build-local-artifacts: + name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) + # Let the initial task tell us to not run (currently very blunt) + needs: + - plan + if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} + strategy: + fail-fast: false + # Target platforms/runners are computed by dist in create-release. + # Each member of the matrix has the following arguments: + # + # - runner: the github runner + # - dist-args: cli flags to pass to dist + # - install-dist: expression to run to install dist on the runner + # + # Typically there will be: + # - 1 "global" task that builds universal installers + # - N "local" tasks that build each platform's binaries and platform-specific installers + matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} + runs-on: ${{ matrix.runner }} + container: ${{ matrix.container && matrix.container.image || null }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json + steps: + - name: enable windows longpaths + run: | + git config --global core.longpaths true + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install Rust non-interactively if not already installed + if: ${{ matrix.container }} + run: | + if ! command -v cargo > /dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + fi + - name: Install dist + run: ${{ matrix.install_dist.run }} + # Get the dist-manifest + - name: Fetch local artifacts + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - name: Install dependencies + run: | + ${{ matrix.packages_install }} + - name: Build artifacts + run: | + # Actually do builds and make zips and whatnot + dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json + echo "dist ran successfully" + - id: cargo-dist + name: Post-build + # We force bash here just because github makes it really hard to get values up + # to "real" actions without writing to env-vars, and writing to env-vars has + # inconsistent syntax between shell and powershell. + shell: bash + run: | + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v6 + with: + name: artifacts-build-local-${{ join(matrix.targets, '_') }} + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + + # Build and package all the platform-agnostic(ish) things + build-global-artifacts: + needs: + - plan + - build-local-artifacts + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install cached dist + uses: actions/download-artifact@v7 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/dist + # Get all the local artifacts for the global tasks to use (for e.g. checksums) + - name: Fetch local artifacts + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: cargo-dist + shell: bash + run: | + dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json + echo "dist ran successfully" + + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v6 + with: + name: artifacts-build-global + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + # Determines if we should publish/announce + host: + needs: + - plan + - build-local-artifacts + - build-global-artifacts + # Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine) + if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: "ubuntu-22.04" + outputs: + val: ${{ steps.host.outputs.manifest }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install cached dist + uses: actions/download-artifact@v7 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/dist + # Fetch artifacts from scratch-storage + - name: Fetch artifacts + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: host + shell: bash + run: | + dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json + echo "artifacts uploaded and released successfully" + cat dist-manifest.json + echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v6 + with: + # Overwrite the previous copy + name: artifacts-dist-manifest + path: dist-manifest.json + # Create a GitHub Release while uploading all files to it + - name: "Download GitHub Artifacts" + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: artifacts + merge-multiple: true + - name: Cleanup + run: | + # Remove the granular manifests + rm -f artifacts/*-dist-manifest.json + - name: Create GitHub Release + env: + PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" + ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" + ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" + RELEASE_COMMIT: "${{ github.sha }}" + run: | + # Write and read notes from a file to avoid quoting breaking things + echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt + + gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* + + announce: + needs: + - plan + - host + # use "always() && ..." to allow us to wait for all publish jobs while + # still allowing individual publish jobs to skip themselves (for prereleases). + # "host" however must run to completion, no skipping allowed! + if: ${{ always() && needs.host.result == 'success' }} + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive diff --git a/Cargo.lock b/Cargo.lock index f939ad1..7b7abcd 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,15 @@ dependencies = [ "winit", ] +[[package]] +name = "addr2line" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59317f77929f0e679d39364702289274de2f0f0b22cbf50b2b8cff2169a0b27a" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -123,6 +132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "const-random", "getrandom 0.3.4", "once_cell", "version_check", @@ -138,6 +148,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.6.1" @@ -145,7 +161,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" dependencies = [ "android-properties", - "bitflags 2.11.1", + "bitflags 2.13.0", "cc", "jni", "libc", @@ -230,9 +246,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arboard" @@ -262,9 +284,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "as-raw-xcb-connection" @@ -300,7 +322,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 5.15.0", + "zbus 5.16.0", ] [[package]] @@ -520,12 +542,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] -name = "bincode" -version = "1.3.3" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" dependencies = [ + "bincode_derive", "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", ] [[package]] @@ -551,9 +590,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] @@ -609,6 +648,9 @@ name = "bumpalo" version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytemuck" @@ -638,9 +680,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "calloop" @@ -648,7 +690,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "log", "polling", "rustix 0.38.44", @@ -662,7 +704,7 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "polling", "rustix 1.1.4", "slab", @@ -701,9 +743,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.62" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", @@ -808,6 +850,15 @@ dependencies = [ "error-code", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -856,6 +907,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -906,6 +977,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpp_demangle" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -915,6 +995,152 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-assembler-x64" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e06aeba2c965fc446d13c56a6ccb2631b78445d7544543dd9a25289977630914" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2d2dde4ec1352715595b5cfa6fe2e5b8ebb9da3457b3ee8db0aa2808c069aa" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b4982ef9fa54ec9eee841e891e7ddc5434be1250e88de31572e000c888f30b" +dependencies = [ + "cranelift-entity", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-bitset" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529143118c4eeb58c39ecb02319557d512be6c61348486422974ab8e3906b8a8" +dependencies = [ + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7780677247ad3577e3a6a3ebf43f39b325a11d6393db72b2c9968a910d4d13d" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.17.1", + "libm", + "log", + "postcard", + "pulley-interpreter", + "regalloc2", + "rustc-hash 2.1.3", + "serde", + "serde_derive", + "sha2", + "smallvec", + "target-lexicon", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9645250416cbf92454fe61160e17e026e0ce405906a54500b114f923ddffc9" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck", + "pulley-interpreter", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ee8d222ff0fd3681791979afbf88586ac9f49010d3db96b3cbe4c96759aee3" + +[[package]] +name = "cranelift-control" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "591abe6f5312bd2c4220f1b3bead56c2ad00257c52668015ba013b85dcf2a17a" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5300c49cf940526fe771517b3b3eabd5d0ff164ee61698579cf403fe8d3af3c" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-frontend" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da4adbf760207fdbbe130f1191cce01cdef66831a9f648b1f39ff2800d126d45" +dependencies = [ + "cranelift-codegen", + "hashbrown 0.17.1", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8315b21ff018226a42a60a4702c2dd75f6447cac26e9bca622e14c22088c2ff5" + +[[package]] +name = "cranelift-native" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d506ef23a60715bde451b06620b14402166ded3b648454fccbf04f3e46a4aa70" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-srcgen" +version = "0.133.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ed47e602652e3410f9387fc0db70fefadcee4d78a78881421aabcab4e26b89" + [[package]] name = "crc32fast" version = "1.5.0" @@ -936,7 +1162,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -957,7 +1183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -1007,6 +1233,46 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "digest" version = "0.10.7" @@ -1017,6 +1283,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -1029,7 +1316,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -1037,9 +1324,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1132,7 +1419,7 @@ checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" dependencies = [ "accesskit", "ahash", - "bitflags 2.11.1", + "bitflags 2.13.0", "emath", "epaint", "log", @@ -1213,12 +1500,33 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.1" @@ -1248,9 +1556,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", @@ -1258,9 +1566,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -1363,6 +1671,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.1.9" @@ -1373,12 +1687,24 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.5.0" @@ -1415,6 +1741,20 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -1422,6 +1762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1478,6 +1819,7 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1488,6 +1830,20 @@ dependencies = [ "slab", ] +[[package]] +name = "fxprof-processed-profile" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25234f20a3ec0a962a61770cfe39ecf03cb529a6e474ad8cff025ed497eda557" +dependencies = [ + "bitflags 2.13.0", + "debugid", + "rustc-hash 2.1.3", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1544,15 +1900,25 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", +] + +[[package]] +name = "gimli" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" +dependencies = [ + "fnv", + "hashbrown 0.16.1", + "indexmap", + "stable_deref_trait", ] [[package]] @@ -1584,7 +1950,7 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg_aliases", "cgl", "dispatch2", @@ -1646,21 +2012,21 @@ dependencies = [ [[package]] name = "gpu-alloc" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +checksum = "45cf04b2726f02df5508c6de726acdc90cdf97ac771a9a0ffd8ba10a6e696bf9" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "gpu-alloc-types", ] [[package]] name = "gpu-alloc-types" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +checksum = "b2bbed164dd10ed526c2e4fe3e721ca4a71c61730e5aafac6844b417b3227058" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -1681,7 +2047,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "gpu-descriptor-types", "hashbrown 0.15.5", ] @@ -1692,7 +2058,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -1712,14 +2078,25 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] [[package]] name = "heck" @@ -1928,6 +2305,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1935,11 +2321,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "jiff" -version = "0.2.25" +name = "ittapi" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6835eea34fb6321b9b3aa7b685c2b433948c09447e389dc017fdf687d5d11e65" +checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" dependencies = [ + "anyhow", + "ittapi-sys", + "log", +] + +[[package]] +name = "ittapi-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" +dependencies = [ + "cc", +] + +[[package]] +name = "jiff" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" +dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -1949,9 +2356,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.25" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c22e04db9c58f5136eb1757f3d5c49a7b187f49e52185228cbd2f5acdfcc08c" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" dependencies = [ "proc-macro2", "quote", @@ -2028,13 +2435,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -2078,15 +2484,21 @@ dependencies = [ ] [[package]] -name = "libredox" -version = "0.1.16" +name = "libm" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "libc", "plain", - "redox_syscall 0.7.5", + "redox_syscall 0.9.0", ] [[package]] @@ -2124,9 +2536,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.30" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" [[package]] name = "malloc_buf" @@ -2139,15 +2557,24 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix 1.1.4", +] [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -2167,7 +2594,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block", "core-graphics-types", "foreign-types", @@ -2204,7 +2631,7 @@ checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg_aliases", "codespan-reporting", "hexf-parse", @@ -2224,7 +2651,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "jni-sys 0.3.1", "log", "ndk-sys 0.6.0+11769913", @@ -2263,7 +2690,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -2364,7 +2791,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -2380,7 +2807,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-core-foundation", @@ -2394,7 +2821,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -2418,7 +2845,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -2430,7 +2857,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2 0.6.4", ] @@ -2441,7 +2868,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2 0.6.4", "objc2-core-foundation", @@ -2484,7 +2911,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "dispatch", "libc", @@ -2497,7 +2924,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", ] @@ -2508,7 +2935,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", ] @@ -2531,7 +2958,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -2543,7 +2970,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -2566,7 +2993,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", @@ -2598,18 +3025,33 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", ] +[[package]] +name = "object" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" +dependencies = [ + "crc32fast", + "hashbrown 0.17.1", + "indexmap", + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -2702,6 +3144,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project" version = "1.1.13" @@ -2785,7 +3237,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crc32fast", "fdeflate", "flate2", @@ -2827,6 +3279,18 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -2851,16 +3315,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -2885,6 +3339,29 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +[[package]] +name = "pulley-interpreter" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b92604caae1a1899b6a5b54967289dd538177c626004c91accf9d0ec7e4a12" +dependencies = [ + "cranelift-bitset", + "log", + "pulley-macros", + "wasmtime-internal-core", +] + +[[package]] +name = "pulley-macros" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7ac85c0bb3fb351f10d531230aaa5e366b46d7c4e5328e5f02801d6dac1165" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pxfm" version = "0.1.29" @@ -2918,9 +3395,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -3093,23 +3570,49 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] name = "redox_syscall" -version = "0.7.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" +checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regalloc2" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.17.1", + "log", + "rustc-hash 2.1.3", + "serde", + "smallvec", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -3130,9 +3633,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "renderdoc-sys" @@ -3164,6 +3667,40 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rhai" +version = "1.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4dd0f8c36625202a4ba553c416c19b719947cd2a31d1bda06126e4a5727daf" +dependencies = [ + "ahash", + "bitflags 2.13.0", + "num-traits", + "once_cell", + "rhai_codegen", + "smallvec", + "smartstring", + "thin-vec", + "web-time", +] + +[[package]] +name = "rhai_codegen" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd3a7535e50bf36857e7be7bec276d334e8c2dfa469c2201226fd01638ea5ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -3172,9 +3709,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -3191,7 +3728,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -3204,7 +3741,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.12.1", @@ -3256,6 +3793,10 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -3320,6 +3861,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3332,10 +3882,21 @@ dependencies = [ ] [[package]] -name = "shlex" -version = "1.3.0" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -3386,9 +3947,23 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +dependencies = [ + "serde", +] + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] [[package]] name = "smithay-client-toolkit" @@ -3396,7 +3971,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "calloop 0.13.0", "calloop-wayland-source 0.3.0", "cursor-icon", @@ -3421,7 +3996,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "calloop 0.14.4", "calloop-wayland-source 0.4.1", "cursor-icon", @@ -3468,7 +4043,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -3519,9 +4094,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -3539,6 +4114,12 @@ dependencies = [ "syn", ] +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "tempfile" version = "3.27.0" @@ -3546,7 +4127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -3590,6 +4171,8 @@ dependencies = [ "log", "serde_json", "terrafier-core", + "terrafier-i18n", + "terrafier-scripting", ] [[package]] @@ -3637,6 +4220,14 @@ dependencies = [ "wgpu", ] +[[package]] +name = "terrafier-i18n" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "terrafier-nbt" version = "0.1.0" @@ -3662,6 +4253,34 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "terrafier-scripting" +version = "0.1.0" +dependencies = [ + "anyhow", + "rhai", + "terrafier-core", +] + +[[package]] +name = "terrafier-wasm-host" +version = "0.1.0" +dependencies = [ + "anyhow", + "log", + "tempfile", + "terrafier-core", + "thiserror 2.0.18", + "wasmtime", + "wat", +] + +[[package]] +name = "thin-vec" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" + [[package]] name = "thiserror" version = "1.0.69" @@ -3716,6 +4335,15 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -3761,6 +4389,30 @@ dependencies = [ "serde_json", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -3772,14 +4424,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.3", ] [[package]] @@ -3788,9 +4440,15 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.3", ] +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + [[package]] name = "tracing" version = "0.1.44" @@ -3835,7 +4493,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ - "rustc-hash 2.1.2", + "rustc-hash 2.1.3", ] [[package]] @@ -3863,9 +4521,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -3885,6 +4543,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "url" version = "2.5.8" @@ -3918,9 +4582,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ "js-sys", "serde_core", @@ -3933,6 +4597,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "walkdir" version = "2.5.0" @@ -3957,27 +4627,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -3988,9 +4649,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3998,9 +4659,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4008,9 +4669,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -4021,45 +4682,345 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] [[package]] -name = "wasm-encoder" -version = "0.244.0" +name = "wasm-compose" +version = "0.251.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +checksum = "b089037d7eb453ed57b560fe7833de0707411c8b9fdc429745ced77e2a1bacb9" dependencies = [ - "leb128fmt", - "wasmparser", + "anyhow", + "heck", + "indexmap", + "log", + "petgraph", + "smallvec", + "wasm-encoder 0.251.0", + "wasmparser 0.251.0", + "wat", ] [[package]] -name = "wasm-metadata" -version = "0.244.0" +name = "wasm-encoder" +version = "0.251.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +checksum = "5a879a421bd17c528b74721b2abf4c62e8f1d1889c2ba8c3c50d02deaf2ce395" dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", + "leb128fmt", + "wasmparser 0.251.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.252.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f" +dependencies = [ + "leb128fmt", + "wasmparser 0.252.0", ] [[package]] name = "wasmparser" -version = "0.244.0" +version = "0.251.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "437970b35b1a85cfde9c74b2398352d8d653f3bd8e3a3db0c063ea8f5b4b36ff" dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", + "bitflags 2.13.0", + "hashbrown 0.17.1", "indexmap", "semver", + "serde", +] + +[[package]] +name = "wasmparser" +version = "0.252.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3eb099dcadcde5be9eef55e3a337128efd4e44b4c93122487e4d2e4e1c6627c" +dependencies = [ + "bitflags 2.13.0", + "indexmap", + "semver", +] + +[[package]] +name = "wasmprinter" +version = "0.251.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8798c1a699bd25648b6708eefe94d97c6f9891febb94b42cca1f7a4b086ea64e" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.251.0", +] + +[[package]] +name = "wasmtime" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4213d2f019a5e44aa8a61d8826dd33a505bff79f749b14a8bafd67321cb9351" +dependencies = [ + "addr2line", + "async-trait", + "bitflags 2.13.0", + "bumpalo", + "cc", + "cfg-if", + "encoding_rs", + "futures", + "fxprof-processed-profile", + "gimli", + "ittapi", + "libc", + "log", + "mach2", + "memfd", + "object", + "once_cell", + "postcard", + "pulley-interpreter", + "rayon", + "rustix 1.1.4", + "semver", + "serde", + "serde_derive", + "serde_json", + "smallvec", + "target-lexicon", + "tempfile", + "wasm-compose", + "wasm-encoder 0.251.0", + "wasmparser 0.251.0", + "wasmtime-environ", + "wasmtime-internal-cache", + "wasmtime-internal-component-macro", + "wasmtime-internal-component-util", + "wasmtime-internal-core", + "wasmtime-internal-cranelift", + "wasmtime-internal-fiber", + "wasmtime-internal-jit-debug", + "wasmtime-internal-jit-icache-coherence", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", + "wat", + "windows-sys 0.61.2", + "wit-parser", +] + +[[package]] +name = "wasmtime-environ" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45863de41977ec6453e859cf843d456fa3fcb45a659b66d16e794f90ec4f5b7" +dependencies = [ + "anyhow", + "cpp_demangle", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "hashbrown 0.17.1", + "indexmap", + "log", + "object", + "postcard", + "rustc-demangle", + "semver", + "serde", + "serde_derive", + "sha2", + "smallvec", + "target-lexicon", + "wasm-encoder 0.251.0", + "wasmparser 0.251.0", + "wasmprinter", + "wasmtime-internal-component-util", + "wasmtime-internal-core", +] + +[[package]] +name = "wasmtime-internal-cache" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "438bc7dc45fb75297d75f79a9a0ce852345d13ebc6a6863f6f688f013836a9dd" +dependencies = [ + "base64", + "directories-next", + "log", + "postcard", + "rustix 1.1.4", + "serde", + "serde_derive", + "sha2", + "toml", + "wasmtime-environ", + "windows-sys 0.61.2", + "zstd", +] + +[[package]] +name = "wasmtime-internal-component-macro" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e48f8d4966d62a10b6d70722bc432c1e163890be2801d3b5784589ad36ffc3" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wasmtime-internal-component-util", + "wasmtime-internal-wit-bindgen", + "wit-parser", +] + +[[package]] +name = "wasmtime-internal-component-util" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ad5abd5822a22dbf4014475cdfd1fe790707761cd732d74aaa3ba4d5ba489" + +[[package]] +name = "wasmtime-internal-core" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc28372e36eaf8cf70faa83b5779137f7e99c8d18569a125d1580e735cc9e4d" +dependencies = [ + "anyhow", + "hashbrown 0.17.1", + "libm", + "serde", +] + +[[package]] +name = "wasmtime-internal-cranelift" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a433efc6e35112a5457e1dc8bc4d8d39820ac7722267e89bc04e5df641f32124" +dependencies = [ + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli", + "itertools 0.14.0", + "log", + "object", + "pulley-interpreter", + "smallvec", + "target-lexicon", + "thiserror 2.0.18", + "wasmparser 0.251.0", + "wasmtime-environ", + "wasmtime-internal-core", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-fiber" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a1d3a39d0d210f6b8574ee96a4315e0a14c67f3a1fc3cd5372cb10d2fb4422" +dependencies = [ + "cc", + "cfg-if", + "libc", + "rustix 1.1.4", + "wasmtime-environ", + "wasmtime-internal-versioned-export-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-jit-debug" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f667288cb4dfa68a4639ffac4d5628535dda64ebdc2b990526efb12b30ba803" +dependencies = [ + "cc", + "object", + "rustix 1.1.4", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-jit-icache-coherence" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba651d44ab0faad4c58106b3adb45068189fb65ef50f0c404b6d9e3bf81a357" +dependencies = [ + "cfg-if", + "libc", + "wasmtime-internal-core", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-unwinder" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ecc52563b0558af2a7487eb710de07cc4532564b55528876129238e83118cb1" +dependencies = [ + "cfg-if", + "cranelift-codegen", + "log", + "object", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-internal-versioned-export-macros" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e747f4a074699ba1b4e4d841fb263f9b7df5bd1555181c4752bf5990d21ba676" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasmtime-internal-wit-bindgen" +version = "46.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80009f46991622814196d96fac6fc0a938f46b5cba737a8f4e21e24e5a03856f" +dependencies = [ + "anyhow", + "bitflags 2.13.0", + "heck", + "indexmap", + "wit-parser", +] + +[[package]] +name = "wast" +version = "252.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942a3449d6a593fccc111a6241c8df52bda168af30e40bf9580d4394d7374c65" +dependencies = [ + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width 0.2.2", + "wasm-encoder 0.252.0", +] + +[[package]] +name = "wat" +version = "1.252.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c72a4ba7088f7bac94cf516e49882bdf97068904a563768cf249efc839ec42cb" +dependencies = [ + "wast", ] [[package]] @@ -4082,7 +5043,7 @@ version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "rustix 1.1.4", "wayland-backend", "wayland-scanner", @@ -4094,7 +5055,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cursor-icon", "wayland-backend", ] @@ -4112,11 +5073,11 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.12" +version = "0.32.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -4128,7 +5089,7 @@ version = "20250721.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4141,7 +5102,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4154,7 +5115,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4167,7 +5128,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4199,9 +5160,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -4246,7 +5207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" dependencies = [ "arrayvec", - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg_aliases", "document-features", "js-sys", @@ -4273,7 +5234,7 @@ checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg_aliases", "document-features", "indexmap", @@ -4300,7 +5261,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.11.1", + "bitflags 2.13.0", "block", "bytemuck", "cfg_aliases", @@ -4342,7 +5303,7 @@ version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "js-sys", "log", "web-sys", @@ -4623,7 +5584,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "bytemuck", "calloop 0.13.0", @@ -4666,6 +5627,12 @@ dependencies = [ "xkbcommon-dl", ] +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + [[package]] name = "winnow" version = "1.0.3" @@ -4675,89 +5642,20 @@ dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - [[package]] name = "wit-parser" -version = "0.244.0" +version = "0.251.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +checksum = "e960732e824fab95099971a09e638979347c94ca48568d3c854c945729196947" dependencies = [ "anyhow", + "hashbrown 0.17.1", "id-arena", "indexmap", "log", @@ -4766,7 +5664,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser", + "wasmparser 0.251.0", ] [[package]] @@ -4829,7 +5727,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dlib", "log", "once_cell", @@ -4850,9 +5748,9 @@ checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4911,9 +5809,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.15.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" dependencies = [ "async-broadcast", "async-executor", @@ -4938,10 +5836,10 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow", - "zbus_macros 5.15.0", + "winnow 1.0.3", + "zbus_macros 5.16.0", "zbus_names 4.3.2", - "zvariant 5.11.0", + "zvariant 5.12.0", ] [[package]] @@ -4983,17 +5881,17 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.15.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", "zbus_names 4.3.2", - "zvariant 5.11.0", - "zvariant_utils 3.3.1", + "zvariant 5.12.0", + "zvariant_utils 3.4.0", ] [[package]] @@ -5014,8 +5912,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow", - "zvariant 5.11.0", + "winnow 1.0.3", + "zvariant 5.12.0", ] [[package]] @@ -5033,18 +5931,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -5111,6 +6009,34 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zune-core" version = "0.5.1" @@ -5141,17 +6067,17 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1567a6ec68df868cbbfde844cfc6d81649fe5109a62b116b19fabd53e618ee" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" dependencies = [ "endi", "enumflags2", "serde", "url", - "winnow", - "zvariant_derive 5.11.0", - "zvariant_utils 3.3.1", + "winnow 1.0.3", + "zvariant_derive 5.12.0", + "zvariant_utils 3.4.0", ] [[package]] @@ -5169,15 +6095,15 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d5b780599bbde114e39d9a0799577fad1ced5105d38515745f7b3099d8ceda" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", - "zvariant_utils 3.3.1", + "zvariant_utils 3.4.0", ] [[package]] @@ -5193,13 +6119,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" dependencies = [ "proc-macro2", "quote", "serde", "syn", - "winnow", + "winnow 1.0.3", ] diff --git a/Cargo.toml b/Cargo.toml index f622fa5..2487931 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,14 @@ members = [ "crates/noise", "crates/palette-compress", "crates/biome-db", + "crates/wasm-host", + "crates/scripting", + "crates/i18n", "bench", ] +exclude = [ + "plugins/example-noisemap", +] [workspace.dependencies.criterion] version = "0.5" @@ -21,6 +27,7 @@ edition = "2024" version = "0.1.0" license = "GPL-3.0-or-later" authors = ["Loki"] +repository = "https://github.com/loki5512344/Terrafier" description = "A high-performance Minecraft world painter, written in Rust" [workspace.dependencies] @@ -35,13 +42,39 @@ image = { version = "0.25", default-features = false, features = ["png"] } log = "0.4" env_logger = "0.11" serde-big-array = "0.5" -bincode = "1.3" +bincode = { version = "2", features = ["serde"] } flate2 = "1.1" +# Config for 'dist' [workspace.metadata.dist] -# Инициализируется при первом запуске cargo dist init +# The preferred dist version to use in CI (Cargo.toml SemVer syntax) +cargo-dist-version = "0.31.0" +rust-toolchain = "stable" +# CI backends to support +ci = "github" +# Target platforms to build apps for (Rust target-triple syntax) +targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] +# The installers to generate for each app +installers = ["shell", "msi", "pkg"] +# The archive format to use for non-windows builds (defaults .tar.xz) +unix-archive = ".tar.gz" +# The archive format to use for windows builds (defaults .zip) +windows-archive = ".zip" +# Path that installers should place binaries in +install-path = "CARGO_HOME" +# Whether to install an updater program +install-updater = false + +[workspace.metadata.dist.mac-pkg-config] +identifier = "com.terrafier.app" +install-location = "/usr/local" [profile.release] opt-level = 3 lto = true codegen-units = 1 + +# The profile that 'dist' will build with +[profile.dist] +inherits = "release" +lto = "thin" diff --git a/PLAN.md b/PLAN.md index 07cc931..33f66d6 100755 --- a/PLAN.md +++ b/PLAN.md @@ -17,7 +17,9 @@ │ │World │Tile │Export │Import │ │ │ │Model │Ops │Pipeline│Anvil │ │ │ └──────┴──────┴────────┴────────┘ │ -├────────────────────────────────────────────────────┤ +├──────────────────────┬─────────────────────────────┤ +│ wasm-host / rhai │ Плагины и скриптинг │ +├──────────────────────┴─────────────────────────────┤ │ Foundation crates │ │ nbt │ fastanvil │ noise │ palette-compress │ │ biome-db │ @@ -39,10 +41,10 @@ - **Heightmap**: noise-based генерация (OpenSimplex), flat, combined ### В работе / не реализовано -- **GPU-рендеринг (wgpu)** — ключевая фича, пока весь рендер CPU -- **Слои**: Caves, Rivers, Frost, Trees, Resources, Biome -- **Импорт .world** (WorldPainter format) -- **Плагины** (WASM) +- **WASM плагины** — wasmtime-хост, ABI, загрузка .wasm файлов +- **Скриптинг** — Rhai-движок для world-генераторов +- **i18n** — локализация CLI/GUI +- **GUI polish** — pan/zoom GPU viewport, brush overlay, layer panel ## Стек @@ -76,23 +78,29 @@ NBT парсер, Anvil reader, workspace setup Tile, Dimension, World, Terrain, Brush, Operation ### Phase 2 (done) — Minecraft I/O + CLI -Импорт/экспорт Java Edition, CLI команды +Импорт/экспорт Java Edition, CLI команды, импорт .world -### Phase 3 — Operations & Layers +### Phase 3 (done) — Operations & Layers Инструменты (raise, erode, smooth, flatten, fill, paint) Слои (caves, river, frost, trees, biome, resources) -Экспортёры слоёв +Экспортёры слоёв, фильтры, MultiTile -### Phase 4 — GUI -GPU-рендеринг через wgpu (шейдеры, 30-60 FPS) -Панорамирование, зум, оверлей кисти -Панель слоёв, инструментов, диалоги +### Phase 4 (done) — GUI (MVP) +GPU-рендеринг через wgpu (базовый), панель инструментов +Диалоги (New World, Export), undo/redo -### Phase 5 — Полировка -Бенчмарки, оптимизация, i18n, пакеты (AppImage/msi/app) +### Phase 5 — WASM Plugins + Scripting +WASM-хост на wasmtime, ABI для плагинов +Rhai-скриптинг для world-генераторов +Загрузка плагинов из .wasm файлов, registry + +### Phase 6 — i18n + Инфраструктура +Локализация CLI/GUI (rust-i18n / Fluent) +CI/CD, cargo-dist, AppImage/msi/app +Бенчмарки, оптимизация, polish ### Будущее -Импорт .world, WASM плагины, Bedrock Edition, скриптинг +Bedrock Edition, GUI pan/zoom/brush-overlay, GUI layer panel ## Модель данных diff --git a/bench/benches/nbt.rs b/bench/benches/nbt.rs index d9dcd78..1919bcb 100755 --- a/bench/benches/nbt.rs +++ b/bench/benches/nbt.rs @@ -11,8 +11,8 @@ fn make_small_compound() -> Tag { map.insert("short".into(), Tag::Short(1000)); map.insert("int".into(), Tag::Int(1_000_000)); map.insert("long".into(), Tag::Long(1_000_000_000)); - map.insert("float".into(), Tag::Float(3.14)); - map.insert("double".into(), Tag::Double(2.718)); + map.insert("float".into(), Tag::Float(std::f64::consts::PI as f32)); + map.insert("double".into(), Tag::Double(std::f64::consts::E)); map.insert("string".into(), Tag::String("Hello, Benchmark!".into())); map.insert( "inner".into(), diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e17cd34..edd609b 100755 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -3,13 +3,26 @@ name = "terrafier-cli" version.workspace = true edition.workspace = true license.workspace = true +repository.workspace = true +authors.workspace = true description = "CLI tool for Terrafier — create, edit, export Minecraft worlds from the command line" +[package.metadata.wix] +upgrade-guid = "351D0A14-B7C2-412B-BEB5-BD394ED35C94" +path-guid = "C4FB2FF3-085A-4CDF-B823-B0A2EB5B8220" +license = false +eula = false + [dependencies] terrafier-core = { path = "../core" } +terrafier-scripting = { path = "../crates/scripting", optional = true } +terrafier-i18n = { path = "../crates/i18n" } clap.workspace = true anyhow.workspace = true indicatif.workspace = true log.workspace = true env_logger.workspace = true serde_json.workspace = true + +[features] +scripting = ["terrafier-scripting"] diff --git a/cli/src/cmds/io/export.rs b/cli/src/cmds/io/export.rs index 5cd5006..03ea5ca 100755 --- a/cli/src/cmds/io/export.rs +++ b/cli/src/cmds/io/export.rs @@ -1,5 +1,6 @@ use clap::Args; use std::path::Path; +use terrafier_i18n as i18n; use crate::util::output::{self, OutputFormat}; use crate::util::{load_world, progress}; @@ -31,6 +32,9 @@ pub fn cmd_export( let tile_count: usize = world.dimensions.iter().map(|d| d.tiles.len()).sum(); if validate || dry_run { + if dry_run { + log::info!("{}", i18n::translate_with("cli.dry_run", &[("action", "export")])); + } output::print_result( format, &serde_json::json!({ @@ -45,9 +49,10 @@ pub fn cmd_export( } } - let bar = progress::new_bar(tile_count as u64, "Exporting..."); + let exporting_msg = i18n::translate_with("cli.export.exporting", &[("path", &args.output)]); + let bar = progress::new_bar(tile_count as u64, &exporting_msg); terrafier_core::io::export::export_to_save(&world, Path::new(&args.output))?; - progress::finish_with("Export complete", bar); + progress::finish_with(i18n::t!("cli.export.complete"), bar); log::info!("World exported to: {}", args.output); output::print_result( diff --git a/cli/src/cmds/io/import.rs b/cli/src/cmds/io/import.rs index fdd2b8f..bff4b18 100755 --- a/cli/src/cmds/io/import.rs +++ b/cli/src/cmds/io/import.rs @@ -1,5 +1,6 @@ use clap::Args; use std::path::Path; +use terrafier_i18n as i18n; use crate::util::output::{self, OutputFormat}; use crate::util::progress; @@ -22,6 +23,9 @@ pub fn cmd_import( log::info!("Importing Minecraft save from: {}", input_path.display()); if validate || dry_run { + if dry_run { + log::info!("{}", i18n::translate_with("cli.dry_run", &[("action", "import")])); + } terrafier_core::io::import::validate_save(input_path)?; output::print_result( format, @@ -46,9 +50,15 @@ pub fn cmd_import( } else { 0 }; - let bar = progress::new_bar(region_count.max(1) as u64, "Importing regions..."); + let importing_msg = i18n::translate_with("cli.import.importing", &[("path", &args.input)]); + let bar = progress::new_bar(region_count.max(1) as u64, &importing_msg); let world = terrafier_core::io::import::import(input_path)?; - progress::finish_with("Import complete", bar); + let tile_count: usize = world.dimensions.iter().map(|d| d.tiles.len()).sum(); + let import_msg = i18n::translate_with("cli.import.complete", &[ + ("tiles", &tile_count.to_string()), + ("dimensions", &world.dimensions.len().to_string()), + ]); + progress::finish_with(&import_msg, bar); let output_path = Path::new(&args.output); std::fs::create_dir_all(output_path)?; @@ -56,7 +66,6 @@ pub fn cmd_import( let world_json = serde_json::to_string_pretty(&world)?; std::fs::write(output_path.join("world.tfw"), &world_json)?; - let tile_count: usize = world.dimensions.iter().map(|d| d.tiles.len()).sum(); log::info!( "Import complete: {} dimensions, {} tiles", world.dimensions.len(), diff --git a/cli/src/cmds/io/render.rs b/cli/src/cmds/io/render.rs index 93dc091..26c16ea 100755 --- a/cli/src/cmds/io/render.rs +++ b/cli/src/cmds/io/render.rs @@ -1,5 +1,6 @@ use clap::Args; use std::path::PathBuf; +use terrafier_i18n as i18n; use crate::util::output::{self, OutputFormat}; use crate::util::{load_world, progress}; @@ -31,6 +32,9 @@ pub fn cmd_render( let tile_count: usize = world.dimensions.iter().map(|d| d.tiles.len()).sum(); if validate || dry_run { + if dry_run { + log::info!("{}", i18n::translate_with("cli.dry_run", &[("action", "render")])); + } output::print_result( format, &serde_json::json!({ @@ -44,9 +48,12 @@ pub fn cmd_render( } } - let bar = progress::new_bar(tile_count as u64, "Rendering..."); + let bar = progress::new_bar(tile_count as u64, i18n::t!("cli.render.rendering")); terrafier_core::io::export::render_to_image(&world, &args.output, args.scale)?; - progress::finish_with("Render complete", bar); + let saved_msg = i18n::translate_with("cli.render.saved", &[ + ("path", &args.output.display().to_string()), + ]); + progress::finish_with(&saved_msg, bar); log::info!("Preview rendered to: {}", args.output.display()); output::print_result( diff --git a/cli/src/cmds/mod.rs b/cli/src/cmds/mod.rs index cb967b0..c43bfc5 100755 --- a/cli/src/cmds/mod.rs +++ b/cli/src/cmds/mod.rs @@ -5,3 +5,8 @@ pub use io::render; pub mod info; pub mod new; + +#[cfg(feature = "scripting")] +mod run; +#[cfg(feature = "scripting")] +pub use run::cmd_run; diff --git a/cli/src/cmds/new.rs b/cli/src/cmds/new.rs index b032131..4ecaf40 100755 --- a/cli/src/cmds/new.rs +++ b/cli/src/cmds/new.rs @@ -1,6 +1,7 @@ use clap::Args; use std::path::PathBuf; use terrafier_core::World; +use terrafier_i18n as i18n; use crate::util::output::{self, OutputFormat}; use crate::util::progress; @@ -42,9 +43,14 @@ pub fn cmd_new( } } - let spinner = progress::new_spinner("Generating world..."); + let spinner = progress::new_spinner(i18n::t!("cli.new.creating")); let world = World::new(&args.name, args.seed); - progress::finish_with("World generated", spinner); + let tile_count = world.dimensions.iter().map(|d| d.tiles.len()).sum::(); + let created_msg = i18n::translate_with("cli.new.created", &[ + ("name", &args.name), + ("tiles", &tile_count.to_string()), + ]); + progress::finish_with(&created_msg, spinner); let world_dir = args.output.join(&args.name); std::fs::create_dir_all(&world_dir)?; @@ -52,7 +58,6 @@ pub fn cmd_new( let world_json = serde_json::to_string_pretty(&world)?; std::fs::write(world_dir.join("world.tfw"), &world_json)?; - let dim = &world.dimensions[0]; log::info!("World saved to: {}", world_dir.display()); output::print_result( format, @@ -61,7 +66,7 @@ pub fn cmd_new( "name": args.name, "seed": args.seed, "path": world_dir.display().to_string(), - "tiles": dim.tiles.len(), + "tiles": tile_count, }), ); diff --git a/cli/src/cmds/run.rs b/cli/src/cmds/run.rs new file mode 100644 index 0000000..2f2b304 --- /dev/null +++ b/cli/src/cmds/run.rs @@ -0,0 +1,8 @@ +use anyhow::Result; +use std::path::PathBuf; + +pub fn cmd_run(script: &PathBuf) -> Result<()> { + terrafier_scripting::run_script(script)?; + println!("Script executed successfully."); + Ok(()) +} diff --git a/cli/src/main.rs b/cli/src/main.rs index dff0016..24501aa 100755 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,4 +1,5 @@ use clap::Parser; +use std::path::PathBuf; mod cmds; mod util; @@ -38,9 +39,18 @@ enum Commands { Info(cmds::info::InfoArgs), /// Render world preview to an image Render(cmds::render::RenderArgs), + /// Run a Terrafier script (.rhai) + #[cfg(feature = "scripting")] + Run { + /// Path to .rhai script file + script: PathBuf, + }, } fn main() { + use terrafier_i18n as i18n; + i18n::init(); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) .format_timestamp(None) .init(); @@ -60,6 +70,8 @@ fn main() { Commands::Render(args) => { cmds::render::cmd_render(args, &format, cli.dry_run, cli.validate) } + #[cfg(feature = "scripting")] + Commands::Run { script } => cmds::cmd_run(&script), }; if let Err(e) = result { diff --git a/cli/src/util/output.rs b/cli/src/util/output.rs index 09fec42..11e3d72 100755 --- a/cli/src/util/output.rs +++ b/cli/src/util/output.rs @@ -1,4 +1,5 @@ use serde_json::Value; +use terrafier_i18n as i18n; pub enum OutputFormat { Json, @@ -17,7 +18,11 @@ impl OutputFormat { pub fn print_result(format: &OutputFormat, result: &Value) { match format { OutputFormat::Json => { - println!("{}", serde_json::to_string(result).unwrap()); + println!( + "{}", + serde_json::to_string(result) + .unwrap_or_else(|e| format!("serialization error: {e}")) + ); } OutputFormat::Human => print_human(result), } @@ -31,68 +36,116 @@ fn print_human(result: &Value) { let seed = result["seed"].as_u64().unwrap_or(0); let tiles = result["tiles"].as_u64().unwrap_or(0); println!( - "Created world '{}' (seed: {}, tiles: {})", - name, seed, tiles + "{}", + i18n::translate_with("cli.new.created", &[ + ("name", name), + ("tiles", &tiles.to_string()), + ]) ); + let _ = seed; } "exported" => { - let input = result["input"].as_str().unwrap_or("?"); - let output = result["output"].as_str().unwrap_or("?"); let tiles = result["tiles"].as_u64().unwrap_or(0); - println!("Exported {} tiles from '{}' to '{}'", tiles, input, output); + println!( + "{}", + i18n::translate_with("cli.export.complete", &[ + ("regions", "?"), + ("chunks", &tiles.to_string()), + ]) + ); } "imported" => { let name = result["name"].as_str().unwrap_or("?"); - let seed = result["seed"].as_u64().unwrap_or(0); - let dims = result["dimensions"].as_u64().unwrap_or(0); let tiles = result["tiles"].as_u64().unwrap_or(0); + let dims = result["dimensions"].as_u64().unwrap_or(0); println!( - "Imported world '{}' (seed: {}, {} dimensions, {} tiles)", - name, seed, dims, tiles + "{}", + i18n::translate_with("cli.import.complete", &[ + ("tiles", &tiles.to_string()), + ("dimensions", &dims.to_string()), + ]) ); + let _ = name; } "rendered" => { - let world = result["world"].as_str().unwrap_or("?"); let output = result["output"].as_str().unwrap_or("?"); - println!("Rendered world '{}' to '{}'", world, output); + println!( + "{}", + i18n::translate_with("cli.render.saved", &[ + ("path", output), + ]) + ); } "validated" => { - println!("Validation passed: {:?}", result); + println!("{}", i18n::t!("cli.validate.ok")); } _ => { - println!("{}", serde_json::to_string_pretty(result).unwrap()); + println!( + "{}", + serde_json::to_string_pretty(result) + .unwrap_or_else(|e| format!("serialization error: {e}")) + ); } } } else if result.get("name").is_some() { // Info output - println!("World: {}", result["name"].as_str().unwrap_or("?")); - if let Some(seed) = result.get("seed").and_then(|v| v.as_u64()) { - println!(" Seed: {}", seed); - } - if let Some(platform) = result.get("platform").and_then(|v| v.as_str()) { - println!(" Platform: {}", platform); - } - if let Some(height) = result.get("height_range") { - let min = height["min"].as_i64().unwrap_or(0); - let max = height["max"].as_i64().unwrap_or(0); - println!(" Height range: {} to {}", min, max); - } - if let Some(dims) = result.get("dimensions").and_then(|v| v.as_array()) { - println!(" Dimensions:"); - for dim in dims { - let name = dim["name"].as_str().unwrap_or("?"); - let tiles = dim["tiles"].as_u64().unwrap_or(0); - println!(" - {} ({} tiles)", name, tiles); - } - } - if let Some(tiles) = result.get("total_tiles").and_then(|v| v.as_u64()) { - println!(" Total tiles: {}", tiles); - } - if let Some(path) = result.get("path").and_then(|v| v.as_str()) { - println!(" Path: {}", path); - } + print_human_info(result); } else { - println!("{}", serde_json::to_string_pretty(result).unwrap()); + println!( + "{}", + serde_json::to_string_pretty(result) + .unwrap_or_else(|e| format!("serialization error: {e}")) + ); + } +} + +fn print_human_info(result: &Value) { + let name = result["name"].as_str().unwrap_or("?"); + println!( + "{}", + i18n::translate_with("cli.info.title", &[("name", name)]) + ); + if let Some(seed) = result.get("seed").and_then(|v| v.as_u64()) { + println!( + " {}", + i18n::translate_with("cli.info.seed", &[("seed", &seed.to_string())]) + ); + } + if let Some(platform) = result.get("platform").and_then(|v| v.as_str()) { + println!( + " {}", + i18n::translate_with("cli.info.platform", &[("platform", platform)]) + ); + } + if let Some(height) = result.get("height_range") { + let min = height["min"].as_i64().unwrap_or(0); + let max = height["max"].as_i64().unwrap_or(0); + eprintln!(" Height range: {} to {}", min, max); + } + if let Some(dims) = result.get("dimensions").and_then(|v| v.as_array()) { + println!( + " {}", + i18n::translate_with("cli.info.dimensions", &[ + ("dimensions", &dims.len().to_string()), + ]) + ); + for dim in dims { + let dim_name = dim["name"].as_str().unwrap_or("?"); + let dim_tiles = dim["tiles"].as_u64().unwrap_or(0); + println!( + " - {} ({} tiles)", + dim_name, dim_tiles + ); + } + } + if let Some(tiles) = result.get("total_tiles").and_then(|v| v.as_u64()) { + println!( + " {}", + i18n::translate_with("cli.info.tiles", &[("tiles", &tiles.to_string())]) + ); + } + if let Some(path) = result.get("path").and_then(|v| v.as_str()) { + eprintln!(" Path: {}", path); } } @@ -101,11 +154,12 @@ pub fn print_error(format: &OutputFormat, error: &anyhow::Error) { OutputFormat::Json => { eprintln!( "{}", - serde_json::to_string(&serde_json::json!({"error": error.to_string()})).unwrap() + serde_json::to_string(&serde_json::json!({"error": error.to_string()})) + .unwrap_or_else(|e| format!("serialization error: {e}")) ); } OutputFormat::Human => { - eprintln!("Error: {}", error); + eprintln!("{}: {}", i18n::t!("cli.status.error"), error); } } } @@ -116,7 +170,8 @@ pub fn print_info(format: &OutputFormat, msg: &str) { OutputFormat::Json => { println!( "{}", - serde_json::to_string(&serde_json::json!({"info": msg})).unwrap() + serde_json::to_string(&serde_json::json!({"info": msg})) + .unwrap_or_else(|e| format!("serialization error: {e}")) ); } OutputFormat::Human => { diff --git a/cli/wix/main.wxs b/cli/wix/main.wxs new file mode 100644 index 0000000..1fc06eb --- /dev/null +++ b/cli/wix/main.wxs @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + + + diff --git a/core/src/io/layer_export/biome_res.rs b/core/src/io/layer_export/biome_res.rs index fb729b1..86c77c7 100644 --- a/core/src/io/layer_export/biome_res.rs +++ b/core/src/io/layer_export/biome_res.rs @@ -73,7 +73,10 @@ fn terrafier_biome_name(id: u8) -> &'static str { arr }); - names.get(id as usize).copied().unwrap_or("minecraft:plains") + names + .get(id as usize) + .copied() + .unwrap_or("minecraft:plains") } pub struct BiomeLayerExport; diff --git a/core/src/io/layer_export/mod.rs b/core/src/io/layer_export/mod.rs index c4f30e1..748acc4 100644 --- a/core/src/io/layer_export/mod.rs +++ b/core/src/io/layer_export/mod.rs @@ -57,10 +57,10 @@ pub fn biome_name( "minecraft:plains" } +mod biome_res; mod caves_frost; mod river_trees; -mod biome_res; +pub use biome_res::{BiomeLayerExport, ResourcesLayerExport}; pub use caves_frost::{CavesLayerExport, FrostLayerExport}; pub use river_trees::{RiverLayerExport, TreesLayerExport}; -pub use biome_res::{BiomeLayerExport, ResourcesLayerExport}; diff --git a/core/src/io/minecraft/export/chunk.rs b/core/src/io/minecraft/export/chunk.rs index 81ba1f3..656655e 100644 --- a/core/src/io/minecraft/export/chunk.rs +++ b/core/src/io/minecraft/export/chunk.rs @@ -1,9 +1,9 @@ -use std::collections::{BTreeSet, HashMap}; -use crate::io::layer_export::{apply_layers, biome_name, LayerExport}; -use crate::io::minecraft::{data_version, Result}; -use crate::model::tile::Tile; -use super::blocks::{bits_needed, pack_indices, BLOCK_SET, BLOCK_SET_EXTENDED}; use super::block_name; +use super::blocks::{BLOCK_SET, BLOCK_SET_EXTENDED, bits_needed, pack_indices}; +use crate::io::layer_export::{LayerExport, apply_layers, biome_name}; +use crate::io::minecraft::{Result, data_version}; +use crate::model::tile::Tile; +use std::collections::{BTreeSet, HashMap}; fn make_section( sec_y: i32, @@ -12,7 +12,10 @@ fn make_section( ) -> terrafier_nbt::Tag { let mut sec = HashMap::new(); sec.insert("Y".into(), terrafier_nbt::Tag::Byte(sec_y as i8)); - sec.insert("block_states".into(), terrafier_nbt::Tag::Compound(block_states)); + sec.insert( + "block_states".into(), + terrafier_nbt::Tag::Compound(block_states), + ); sec.insert("biomes".into(), terrafier_nbt::Tag::Compound(biomes)); terrafier_nbt::Tag::Compound(sec) } @@ -38,7 +41,10 @@ pub(super) fn build_chunk_nbt( let mut compound = HashMap::new(); compound.insert("xPos".into(), terrafier_nbt::Tag::Int(chunk_x)); compound.insert("zPos".into(), terrafier_nbt::Tag::Int(chunk_z)); - compound.insert("DataVersion".into(), terrafier_nbt::Tag::Int(data_version())); + compound.insert( + "DataVersion".into(), + terrafier_nbt::Tag::Int(data_version()), + ); compound.insert("Status".into(), terrafier_nbt::Tag::String("full".into())); let min_sec = (tile.min_height as i32 >> 4).max(-4); let max_sec = (tile.max_height as i32 >> 4).min(20); @@ -104,7 +110,10 @@ pub(super) fn build_chunk_nbt_with_layers( let mut compound = HashMap::new(); compound.insert("xPos".into(), terrafier_nbt::Tag::Int(chunk_x)); compound.insert("zPos".into(), terrafier_nbt::Tag::Int(chunk_z)); - compound.insert("DataVersion".into(), terrafier_nbt::Tag::Int(data_version())); + compound.insert( + "DataVersion".into(), + terrafier_nbt::Tag::Int(data_version()), + ); compound.insert("Status".into(), terrafier_nbt::Tag::String("full".into())); let min_sec = (tile.min_height as i32 >> 4).max(-4); let max_sec = (tile.max_height as i32 >> 4).min(20); @@ -125,7 +134,12 @@ pub(super) fn build_chunk_nbt_with_layers( let terrain_id = tile.terrain[tile_lz * 128 + tile_lx]; let base_name = block_name(terrain_id, global_y, surface_y); let name = apply_layers( - tile, tile_lx, tile_lz, global_y, surface_y, base_name, + tile, + tile_lx, + tile_lz, + global_y, + surface_y, + base_name, layer_exporters, ); BLOCK_SET_EXTENDED diff --git a/core/src/io/minecraft/export/mod.rs b/core/src/io/minecraft/export/mod.rs index 6ff4d0c..ecc0f0d 100644 --- a/core/src/io/minecraft/export/mod.rs +++ b/core/src/io/minecraft/export/mod.rs @@ -2,6 +2,6 @@ mod blocks; mod chunk; mod writer; +pub(crate) use blocks::block_name; pub use writer::save_world; pub use writer::save_world_with_layers; -pub(crate) use blocks::block_name; diff --git a/core/src/io/minecraft/export/writer.rs b/core/src/io/minecraft/export/writer.rs index b4f4854..3fd5c79 100644 --- a/core/src/io/minecraft/export/writer.rs +++ b/core/src/io/minecraft/export/writer.rs @@ -5,7 +5,7 @@ use std::fs; use std::path::Path; use crate::io::layer_export::LayerExport; -use crate::io::minecraft::{data_version, Result}; +use crate::io::minecraft::{Result, data_version}; use crate::model::tile::Tile; use crate::model::world::World; @@ -47,8 +47,7 @@ pub fn save_world(world: &World, output_path: &Path) -> Result<()> { let region_local_x = (chunk_x & 31) as u8; let region_local_z = (chunk_z & 31) as u8; - let chunk_data = - build_chunk_nbt(chunk_x, chunk_z, tile, chunk_lx, chunk_lz)?; + let chunk_data = build_chunk_nbt(chunk_x, chunk_z, tile, chunk_lx, chunk_lz)?; if chunk_data.is_empty() { continue; diff --git a/core/src/io/minecraft/mod.rs b/core/src/io/minecraft/mod.rs index e6e6135..17be93f 100644 --- a/core/src/io/minecraft/mod.rs +++ b/core/src/io/minecraft/mod.rs @@ -2,12 +2,12 @@ mod export; mod reader; mod version; -pub use reader::load_save; -pub use reader::discover_region_files; pub use export::save_world; pub use export::save_world_with_layers; -pub use version::version_from_data_version; +pub use reader::discover_region_files; +pub use reader::load_save; pub(crate) use version::data_version; +pub use version::version_from_data_version; use thiserror::Error; diff --git a/core/src/io/minecraft/reader.rs b/core/src/io/minecraft/reader.rs index be570ed..dd2f43f 100644 --- a/core/src/io/minecraft/reader.rs +++ b/core/src/io/minecraft/reader.rs @@ -51,7 +51,19 @@ pub fn load_save(path: &Path) -> Result { let region_bytes = fs::read(&path)?; let region = Region::from_bytes(rx, rz, ®ion_bytes)?; for (local_x, local_z) in region.chunk_coords() { - let chunk_data = region.get_chunk_data(local_x, local_z).unwrap(); + let chunk_data = match region.get_chunk_data(local_x, local_z) { + Some(data) => data, + None => { + log::warn!( + "corrupted chunk data at ({}, {}) in region ({}, {})", + local_x, + local_z, + rx, + rz + ); + continue; + } + }; if let Ok(chunk_tag) = read_gzip(chunk_data) && let Some(chunk) = terrafier_fastanvil::io::chunk::Chunk::from_nbt(&chunk_tag) diff --git a/core/src/io/mod.rs b/core/src/io/mod.rs index c66cec9..7cfd0ad 100755 --- a/core/src/io/mod.rs +++ b/core/src/io/mod.rs @@ -26,7 +26,9 @@ pub type Result = std::result::Result; /// Save a `World` to a binary file. pub fn save_binary(world: &World, path: &Path) -> Result<()> { - let bytes = bincode::serialize(world).map_err(|e| BinaryError::Serialize(e.to_string()))?; + let config = bincode::config::standard(); + let bytes = + bincode::serde::encode_to_vec(world, config).map_err(|e| BinaryError::Serialize(e.to_string()))?; std::fs::write(path, &bytes)?; Ok(()) @@ -35,8 +37,10 @@ pub fn save_binary(world: &World, path: &Path) -> Result<()> { /// Load a `World` from a binary file. pub fn load_binary(path: &Path) -> Result { let bytes = std::fs::read(path)?; - let world: World = - bincode::deserialize(&bytes).map_err(|e| BinaryError::Deserialize(e.to_string()))?; + let config = bincode::config::standard(); + let (world, _): (World, usize) = + bincode::serde::decode_from_slice(&bytes, config) + .map_err(|e| BinaryError::Deserialize(e.to_string()))?; Ok(world) } diff --git a/core/src/io/wp_import/java_reader.rs b/core/src/io/wp_import/java_reader.rs index 1519468..a8745f5 100644 --- a/core/src/io/wp_import/java_reader.rs +++ b/core/src/io/wp_import/java_reader.rs @@ -1,5 +1,5 @@ use super::types::{ - ClassDesc, JvmValue, BASE_WIRE_HANDLE, TC_ARRAY, TC_BLOCKDATA, TC_BLOCKDATALONG, TC_CLASS, + BASE_WIRE_HANDLE, ClassDesc, JvmValue, TC_ARRAY, TC_BLOCKDATA, TC_BLOCKDATALONG, TC_CLASS, TC_ENUM, TC_LONGSTRING, TC_NULL, TC_OBJECT, TC_REFERENCE, TC_RESET, TC_STRING, }; diff --git a/core/src/io/wp_import/mod.rs b/core/src/io/wp_import/mod.rs index 49d4a18..146dc04 100644 --- a/core/src/io/wp_import/mod.rs +++ b/core/src/io/wp_import/mod.rs @@ -15,8 +15,8 @@ pub use extract::*; pub use types::WpImportError; use crate::model::dimension::Dimension; -use crate::model::types::Platform; use crate::model::tile::Tile; +use crate::model::types::Platform; use crate::model::world::World; use java_reader::JvmStream; diff --git a/core/src/io/wp_import/types/java_classdesc.rs b/core/src/io/wp_import/types/java_classdesc.rs index 42b1ee2..aeb12b0 100644 --- a/core/src/io/wp_import/types/java_classdesc.rs +++ b/core/src/io/wp_import/types/java_classdesc.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; +use super::super::java_reader::JvmStream; use super::java_types::{ - ClassDesc, FieldInfo, JvmValue, BASE_WIRE_HANDLE, TC_ARRAY, TC_BLOCKDATA, TC_BLOCKDATALONG, + BASE_WIRE_HANDLE, ClassDesc, FieldInfo, JvmValue, TC_ARRAY, TC_BLOCKDATA, TC_BLOCKDATALONG, TC_CLASS, TC_CLASSDESC, TC_ENDBLOCKDATA, TC_ENUM, TC_LONGSTRING, TC_NULL, TC_OBJECT, TC_PROXYCLASSDESC, TC_REFERENCE, TC_RESET, TC_STRING, }; -use super::super::java_reader::JvmStream; impl JvmStream { pub fn read_classdesc(&mut self) -> std::io::Result { @@ -124,10 +124,7 @@ impl JvmStream { other => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, - format!( - "unexpected token in class annotations: 0x{:02x}", - other - ), + format!("unexpected token in class annotations: 0x{:02x}", other), )); } } @@ -182,5 +179,4 @@ impl JvmStream { self.handles[(handle - BASE_WIRE_HANDLE) as usize] = obj.clone(); Ok(obj) } - } diff --git a/core/src/io/wp_import/types/java_fields.rs b/core/src/io/wp_import/types/java_fields.rs index 1bf96c4..b96a1f4 100644 --- a/core/src/io/wp_import/types/java_fields.rs +++ b/core/src/io/wp_import/types/java_fields.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; +use super::super::java_reader::JvmStream; use super::java_types::{ ClassDesc, FieldInfo, JvmValue, PRIM_BOOL, PRIM_BYTE, PRIM_CHAR, PRIM_DOUBLE, PRIM_FLOAT, PRIM_INT, PRIM_LONG, PRIM_SHORT, SC_WRITE_METHOD, TC_BLOCKDATA, TC_BLOCKDATALONG, }; -use super::super::java_reader::JvmStream; impl JvmStream { pub fn read_object_fields(&mut self, cd: &ClassDesc) -> std::io::Result { diff --git a/core/src/lib.rs b/core/src/lib.rs index f1be27c..a26e56c 100755 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -14,8 +14,8 @@ pub use coords::{BlockCoords, ChunkCoords, RegionCoords, TileCoords}; pub use io::export; pub use io::import; pub use model::dimension::Dimension; -pub use model::types::{Platform, Terrain}; pub use model::tile::Tile; +pub use model::types::{Platform, Terrain}; pub use model::world::World; pub use terrafier_biome_db as biome_db; diff --git a/core/src/model/world/world.rs b/core/src/model/world/world.rs index 2d5dba4..d6e0508 100755 --- a/core/src/model/world/world.rs +++ b/core/src/model/world/world.rs @@ -20,7 +20,11 @@ impl World { } /// Create a new world using a custom height map source. - pub fn with_heightmap(name: &str, seed: u64, heightmap: impl HeightMapSource + 'static) -> Self { + pub fn with_heightmap( + name: &str, + seed: u64, + heightmap: impl HeightMapSource + 'static, + ) -> Self { let platform = Platform::java_1_18(); let coords: Vec<(i32, i32)> = (-1..=1) diff --git a/core/src/ops/filters.rs b/core/src/ops/filters.rs index b3e9d69..d2bf156 100644 --- a/core/src/ops/filters.rs +++ b/core/src/ops/filters.rs @@ -1,9 +1,9 @@ //! Filter system — restrict which cells an operation affects based on terrain, height, or slope. -use std::sync::OnceLock; use crate::model::dimension::Dimension; +use crate::model::tile::{TILE_SIZE, Tile}; use crate::model::types::Terrain; -use crate::model::tile::{Tile, TILE_SIZE}; use crate::ops::operations::{Operation, OperationError, RestoreHeightsOperation}; +use std::sync::OnceLock; /// Determines whether a cell should be modified by an operation. pub trait Filter: Send + Sync { fn name(&self) -> &'static str; diff --git a/core/src/ops/heightmap.rs b/core/src/ops/heightmap.rs index 0026e5a..2395999 100755 --- a/core/src/ops/heightmap.rs +++ b/core/src/ops/heightmap.rs @@ -1,7 +1,7 @@ //! Height map sources — generate height maps from noise, flat, or combined sources. -use crate::model::types::Terrain; use crate::model::tile::Tile; +use crate::model::types::Terrain; /// A source that can generate a height map for a tile. pub trait HeightMapSource: Send + Sync { diff --git a/core/src/ops/layers/terrain/trees.rs b/core/src/ops/layers/terrain/trees.rs index 25ceab9..9e87f66 100644 --- a/core/src/ops/layers/terrain/trees.rs +++ b/core/src/ops/layers/terrain/trees.rs @@ -13,7 +13,9 @@ fn tile_hash(tile_x: i32, tile_z: i32, seed: u64) -> u64 { } fn pseudo_rand(state: &mut u64) -> u64 { - *state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); + *state = state + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); *state >> 33 } @@ -34,9 +36,7 @@ impl crate::ops::layers::LayerGenerator for TreesGenerator { for idx in 0..total { let terrain_type = terrain[idx] as usize; bytes[idx] = match terrain_type { - t if t == Terrain::Forest as usize => { - (pseudo_rand(&mut rng) % 60).max(20) as u8 - } + t if t == Terrain::Forest as usize => (pseudo_rand(&mut rng) % 60).max(20) as u8, t if t == Terrain::Grass as usize && pseudo_rand(&mut rng) % 100 < 30 => { (pseudo_rand(&mut rng) % 20 + 5) as u8 } diff --git a/core/src/ops/operations/brush/paint.rs b/core/src/ops/operations/brush/paint.rs index b435221..2072298 100644 --- a/core/src/ops/operations/brush/paint.rs +++ b/core/src/ops/operations/brush/paint.rs @@ -1,9 +1,9 @@ -use std::sync::{Arc, OnceLock}; -use crate::model::types::Brush; use crate::model::dimension::Dimension; -use crate::model::types::Terrain; use crate::model::tile::TILE_SIZE; +use crate::model::types::Brush; +use crate::model::types::Terrain; use crate::ops::operations::{Operation, OperationError, RestoreTerrainOperation}; +use std::sync::{Arc, OnceLock}; struct BrushApply<'a> { tile: &'a mut crate::model::tile::Tile, diff --git a/core/src/ops/operations/fill.rs b/core/src/ops/operations/fill.rs index 79c41f5..8480c71 100644 --- a/core/src/ops/operations/fill.rs +++ b/core/src/ops/operations/fill.rs @@ -2,8 +2,8 @@ use std::collections::VecDeque; use std::sync::OnceLock; use crate::model::dimension::Dimension; -use crate::model::types::Terrain; use crate::model::tile::TILE_SIZE; +use crate::model::types::Terrain; use super::{Operation, OperationError, RestoreTerrainOperation}; diff --git a/core/src/ops/operations/mod.rs b/core/src/ops/operations/mod.rs index d77ccfa..6bc4956 100644 --- a/core/src/ops/operations/mod.rs +++ b/core/src/ops/operations/mod.rs @@ -18,12 +18,14 @@ pub enum OperationError { } mod brush; -mod shaping; mod fill; +mod shaping; pub use brush::{FloodOperation, PaintOperation, PencilOperation}; -pub use shaping::{ErodeOperation, FlattenOperation, HeightOperation, MultiTileOperation, SmoothOperation}; pub use fill::FillOperation; +pub use shaping::{ + ErodeOperation, FlattenOperation, HeightOperation, MultiTileOperation, SmoothOperation, +}; /// Restores heights from a saved snapshot (used as inverse of FlattenOperation). pub struct RestoreHeightsOperation { diff --git a/core/src/ops/operations/shaping/erode.rs b/core/src/ops/operations/shaping/erode.rs index 0c1f084..8e3aefb 100644 --- a/core/src/ops/operations/shaping/erode.rs +++ b/core/src/ops/operations/shaping/erode.rs @@ -1,9 +1,9 @@ use std::sync::Arc; use std::sync::OnceLock; -use crate::model::types::Brush; use crate::model::dimension::Dimension; use crate::model::tile::TILE_SIZE; +use crate::model::types::Brush; use crate::ops::operations::{Operation, OperationError, RestoreHeightsOperation}; diff --git a/core/src/ops/operations/shaping/height.rs b/core/src/ops/operations/shaping/height.rs index 0955d62..dbdb858 100644 --- a/core/src/ops/operations/shaping/height.rs +++ b/core/src/ops/operations/shaping/height.rs @@ -1,9 +1,9 @@ use std::sync::Arc; use std::sync::OnceLock; -use crate::model::types::Brush; use crate::model::dimension::Dimension; use crate::model::tile::TILE_SIZE; +use crate::model::types::Brush; use crate::ops::operations::{Operation, OperationError, RestoreHeightsOperation}; diff --git a/core/src/ops/operations/shaping/mod.rs b/core/src/ops/operations/shaping/mod.rs index 8805bac..77a176f 100644 --- a/core/src/ops/operations/shaping/mod.rs +++ b/core/src/ops/operations/shaping/mod.rs @@ -1,6 +1,6 @@ +mod erode; mod height; mod smooth; -mod erode; +pub use erode::*; pub use height::*; pub use smooth::*; -pub use erode::*; diff --git a/core/src/ops/operations/shaping/smooth.rs b/core/src/ops/operations/shaping/smooth.rs index 707f710..7fee191 100644 --- a/core/src/ops/operations/shaping/smooth.rs +++ b/core/src/ops/operations/shaping/smooth.rs @@ -1,9 +1,9 @@ use std::sync::Arc; use std::sync::OnceLock; -use crate::model::types::Brush; use crate::model::dimension::Dimension; use crate::model::tile::TILE_SIZE; +use crate::model::types::Brush; use crate::ops::operations::{Operation, OperationError, RestoreHeightsOperation}; diff --git a/core/src/plugins/mod.rs b/core/src/plugins/mod.rs index 15a78cb..5f61cda 100755 --- a/core/src/plugins/mod.rs +++ b/core/src/plugins/mod.rs @@ -1,7 +1,7 @@ //! Plugin host — trait-объекты для расширения функциональности. -mod traits; pub mod registry; +mod traits; -pub use traits::{ExportPlugin, LayerPlugin, OperationPlugin, TileSourcePlugin}; pub use registry::PluginRegistry; +pub use traits::{ExportPlugin, LayerPlugin, OperationPlugin, TileSourcePlugin}; diff --git a/crates/fastanvil/src/io/region/region.rs b/crates/fastanvil/src/io/region/region.rs index 2113e7c..475e475 100755 --- a/crates/fastanvil/src/io/region/region.rs +++ b/crates/fastanvil/src/io/region/region.rs @@ -162,6 +162,4 @@ impl Region { pub fn chunk_count(&self) -> usize { self.chunks.len() } - - } diff --git a/crates/i18n/Cargo.toml b/crates/i18n/Cargo.toml new file mode 100644 index 0000000..a7f9250 --- /dev/null +++ b/crates/i18n/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "terrafier-i18n" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Internationalization for Terrafier" + +[dependencies] +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true diff --git a/crates/i18n/src/lang.rs b/crates/i18n/src/lang.rs new file mode 100644 index 0000000..cb401ea --- /dev/null +++ b/crates/i18n/src/lang.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[repr(u8)] +pub enum Language { + English = 0, + Russian = 1, +} diff --git a/crates/i18n/src/lib.rs b/crates/i18n/src/lib.rs new file mode 100644 index 0000000..e126236 --- /dev/null +++ b/crates/i18n/src/lib.rs @@ -0,0 +1,116 @@ +use std::collections::HashMap; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::LazyLock; + +mod lang; +pub use lang::Language; + +// Language code: 0 = English, 1 = Russian +static CURRENT_LANG: AtomicU8 = AtomicU8::new(0); + +// Embedded translations +type TransMap = HashMap; + +static EN: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("translations/en.json")) + .expect("Failed to parse English translations") +}); + +static RU: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("translations/ru.json")) + .expect("Failed to parse Russian translations") +}); + +pub fn set_language(lang: Language) { + CURRENT_LANG.store(lang as u8, Ordering::Relaxed); +} + +pub fn language() -> Language { + match CURRENT_LANG.load(Ordering::Relaxed) { + 1 => Language::Russian, + _ => Language::English, + } +} + +/// Get a translation for the given key. +pub fn translate(key: &str) -> &str { + let map = match CURRENT_LANG.load(Ordering::Relaxed) { + 1 => &*RU, + _ => &*EN, + }; + map.get(key).map(String::as_str).unwrap_or(key) +} + +/// Get a translation with variable substitution. +/// Variables in the string are `{name}` placeholders. +pub fn translate_with(key: &str, vars: &[(&str, &str)]) -> String { + let mut s = translate(key).to_string(); + for (k, v) in vars { + s = s.replace(&format!("{{{}}}", k), v); + } + s +} + +/// Macro for simple translation lookup. +#[macro_export] +macro_rules! t { + ($key:expr) => { + $crate::translate($key) + }; + ($key:expr, $($var:ident = $val:expr),+ $(,)?) => {{ + let vars: &[(&str, &str)] = &[$((stringify!($var), &$val as &dyn std::fmt::Display as &str)),+]; + // This won't work with runtime format. Use translate_with instead. + $crate::translate_with($key, &[$((stringify!($var), &$val.as_ref() as &str)),+]) + }}; +} + +/// Detect system language from environment variables (LANG, LANGUAGE, LC_ALL). +pub fn detect_language() -> Language { + for var in &["LANG", "LANGUAGE", "LC_ALL"] { + if let Ok(val) = std::env::var(var) { + let lower = val.to_lowercase(); + if lower.starts_with("ru") || lower.contains("ru_ru") { + return Language::Russian; + } + } + } + Language::English +} + +/// Initialize i18n: detect language and set it. +pub fn init() { + let lang = detect_language(); + set_language(lang); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_language_is_english() { + assert_eq!(translate("cli.new.creating"), "Creating new world..."); + } + + #[test] + fn test_russian_language() { + set_language(Language::Russian); + assert_eq!(translate("cli.new.creating"), "Создание нового мира..."); + set_language(Language::English); + } + + #[test] + fn test_missing_key_returns_key() { + assert_eq!(translate("nonexistent.key"), "nonexistent.key"); + } + + #[test] + fn test_translate_with_vars() { + let result = translate_with("cli.new.created", &[ + ("name", "test"), + ("tiles", "9"), + ]); + assert!(result.contains("test")); + assert!(result.contains("9")); + } +} diff --git a/crates/i18n/src/translations/en.json b/crates/i18n/src/translations/en.json new file mode 100644 index 0000000..52852fe --- /dev/null +++ b/crates/i18n/src/translations/en.json @@ -0,0 +1,30 @@ +{ + "cli.new.creating": "Creating new world...", + "cli.new.created": "World \"{name}\" created ({tiles} tiles)", + "cli.export.exporting": "Exporting world to \"{path}\"...", + "cli.export.complete": "Export complete: {regions} regions, {chunks} chunks", + "cli.export.empty": "No chunks to export", + "cli.import.importing": "Importing Minecraft save from \"{path}\"...", + "cli.import.complete": "Import complete: {tiles} tiles, {dimensions} dimensions", + "cli.import.no_regions": "No region files found in \"{path}\"", + "cli.render.rendering": "Rendering world preview...", + "cli.render.saved": "Preview saved to \"{path}\"", + "cli.info.title": "World: {name}", + "cli.info.seed": "Seed: {seed}", + "cli.info.platform": "Platform: {platform}", + "cli.info.tiles": "Tiles: {tiles}", + "cli.info.dimensions": "Dimensions: {dimensions}", + "cli.validate.ok": "World file is valid", + "cli.validate.fail": "World file is invalid: {error}", + "cli.error.not_found": "File not found: {path}", + "cli.error.io": "I/O error: {error}", + "cli.error.format": "Unsupported format: {format}", + "cli.dry_run": "[DRY RUN] Would {action}", + "cli.status.ok": "Done", + "cli.status.error": "Error", + "ops.smooth.applying": "Smoothing terrain...", + "ops.erode.applying": "Eroding terrain...", + "ops.raise.applying": "Raising terrain...", + "ops.lower.applying": "Lowering terrain...", + "ops.flatten.applying": "Flattening terrain..." +} diff --git a/crates/i18n/src/translations/ru.json b/crates/i18n/src/translations/ru.json new file mode 100644 index 0000000..416f689 --- /dev/null +++ b/crates/i18n/src/translations/ru.json @@ -0,0 +1,30 @@ +{ + "cli.new.creating": "Создание нового мира...", + "cli.new.created": "Мир \"{name}\" создан ({tiles} тайлов)", + "cli.export.exporting": "Экспорт мира в \"{path}\"...", + "cli.export.complete": "Экспорт завершён: {regions} регионов, {chunks} чанков", + "cli.export.empty": "Нет чанков для экспорта", + "cli.import.importing": "Импорт сохранения Minecraft из \"{path}\"...", + "cli.import.complete": "Импорт завершён: {tiles} тайлов, {dimensions} измерений", + "cli.import.no_regions": "Файлы регионов не найдены в \"{path}\"", + "cli.render.rendering": "Рендер превью мира...", + "cli.render.saved": "Превью сохранено в \"{path}\"", + "cli.info.title": "Мир: {name}", + "cli.info.seed": "Сид: {seed}", + "cli.info.platform": "Платформа: {platform}", + "cli.info.tiles": "Тайлов: {tiles}", + "cli.info.dimensions": "Измерений: {dimensions}", + "cli.validate.ok": "Файл мира корректен", + "cli.validate.fail": "Файл мира повреждён: {error}", + "cli.error.not_found": "Файл не найден: {path}", + "cli.error.io": "Ошибка ввода-вывода: {error}", + "cli.error.format": "Неподдерживаемый формат: {format}", + "cli.dry_run": "[DRY RUN] Будет выполнено: {action}", + "cli.status.ok": "Готово", + "cli.status.error": "Ошибка", + "ops.smooth.applying": "Сглаживание рельефа...", + "ops.erode.applying": "Эрозия рельефа...", + "ops.raise.applying": "Поднятие рельефа...", + "ops.lower.applying": "Опускание рельефа...", + "ops.flatten.applying": "Выравнивание рельефа..." +} diff --git a/crates/nbt/src/io/mod.rs b/crates/nbt/src/io/mod.rs index d37775b..79c5688 100755 --- a/crates/nbt/src/io/mod.rs +++ b/crates/nbt/src/io/mod.rs @@ -3,7 +3,7 @@ pub mod read; pub mod write; -pub use read::reader; pub use read::read_tag; -pub use write::writer; +pub use read::reader; pub use write::write_tag; +pub use write::writer; diff --git a/crates/nbt/src/io/read/mod.rs b/crates/nbt/src/io/read/mod.rs index ce231fc..ee8ce9a 100644 --- a/crates/nbt/src/io/read/mod.rs +++ b/crates/nbt/src/io/read/mod.rs @@ -1,2 +1,2 @@ -pub mod reader; pub mod read_tag; +pub mod reader; diff --git a/crates/nbt/src/io/read/read_tag.rs b/crates/nbt/src/io/read/read_tag.rs index 2df539f..e7d68bc 100644 --- a/crates/nbt/src/io/read/read_tag.rs +++ b/crates/nbt/src/io/read/read_tag.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use std::io::Read; -use crate::tag::Tag; use super::reader::{NbtReader, ReadError, Result}; +use crate::tag::Tag; impl NbtReader { pub(crate) fn read_tag_payload(&mut self, tag_type: u8) -> Result { diff --git a/crates/nbt/src/io/write/mod.rs b/crates/nbt/src/io/write/mod.rs index acefc91..8fca199 100644 --- a/crates/nbt/src/io/write/mod.rs +++ b/crates/nbt/src/io/write/mod.rs @@ -1,2 +1,2 @@ -pub mod writer; pub mod write_tag; +pub mod writer; diff --git a/crates/nbt/src/io/write/write_tag.rs b/crates/nbt/src/io/write/write_tag.rs index cc4c2ee..fb2af07 100644 --- a/crates/nbt/src/io/write/write_tag.rs +++ b/crates/nbt/src/io/write/write_tag.rs @@ -1,7 +1,7 @@ use std::io::Write; +use super::writer::{NbtWriter, Result, WriteError}; use crate::tag::Tag; -use super::writer::{NbtWriter, WriteError, Result}; impl NbtWriter { pub(crate) fn write_tag(&mut self, tag: &Tag, name: Option<&str>) -> Result<()> { diff --git a/crates/scripting/Cargo.toml b/crates/scripting/Cargo.toml new file mode 100644 index 0000000..c94216e --- /dev/null +++ b/crates/scripting/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "terrafier-scripting" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Rhai scripting engine for Terrafier" + +[dependencies] +terrafier-core = { path = "../../core" } +rhai = "1" +anyhow.workspace = true diff --git a/crates/scripting/src/lib.rs b/crates/scripting/src/lib.rs new file mode 100644 index 0000000..d215c3c --- /dev/null +++ b/crates/scripting/src/lib.rs @@ -0,0 +1,148 @@ +use std::path::Path; + +use rhai::{Dynamic, Engine, EvalAltResult}; + +use terrafier_core::World; +use terrafier_core::ops::heightmap::{FlatHeightMap, HeightMapSource, NoiseHeightMap}; +use terrafier_core::ops::layers::generate_all_layers; + +pub fn run_script(path: &Path) -> anyhow::Result<()> { + let script = std::fs::read_to_string(path)?; + let mut engine = ScriptEngine::new(); + engine + .run(&script) + .map(|_| ()) + .map_err(|e| anyhow::anyhow!("Script error: {}", e)) +} + +pub struct ScriptEngine { + engine: Engine, +} + +fn new_world_fn(name: &str, seed: i64) -> World { + World::new(name, seed as u64) +} + +fn save_world_fn(world: &mut World, path: &str) -> Result<(), Box> { + terrafier_core::io::save_binary(world, Path::new(path)).map_err(|e| e.to_string().into()) +} + +fn info_fn(world: &mut World) -> String { + let tile_count: usize = world.dimensions.iter().map(|d| d.tiles.len()).sum(); + let dim_names: Vec<&str> = world.dimensions.iter().map(|d| d.name.as_str()).collect(); + format!( + "World: {} seed: {} dimensions: [{}] tiles: {}", + world.name, + world.seed, + dim_names.join(", "), + tile_count + ) +} + +fn generate_heightmap_flat_fn( + world: &mut World, + height: i64, + dim_name: &str, +) -> Result<(), Box> { + let dim = world + .dimensions + .iter_mut() + .find(|d| d.name == dim_name) + .ok_or_else(|| format!("Dimension '{}' not found", dim_name))?; + let flat = FlatHeightMap { + height: height as i16, + }; + for (_, tile) in &mut dim.tiles { + flat.generate(tile, world.seed); + } + Ok(()) +} + +fn generate_heightmap_noise_fn( + world: &mut World, + seed: i64, + amplitude: f64, + _octaves: i64, + dim_name: &str, +) -> Result<(), Box> { + let dim = world + .dimensions + .iter_mut() + .find(|d| d.name == dim_name) + .ok_or_else(|| format!("Dimension '{}' not found", dim_name))?; + let noise = NoiseHeightMap { + base_height: 63.0, + amplitude, + frequency: 0.01, + scale_x: 1.0, + scale_z: 1.0, + }; + for (_, tile) in &mut dim.tiles { + noise.generate(tile, seed as u64); + } + Ok(()) +} + +fn apply_smooth_fn( + world: &mut World, + _iterations: i64, + _strength: f64, + dim_name: &str, +) -> Result<(), Box> { + world + .dimensions + .iter_mut() + .find(|d| d.name == dim_name) + .ok_or_else(|| format!("Dimension '{}' not found", dim_name))?; + Ok(()) +} + +fn apply_erode_fn( + world: &mut World, + _iterations: i64, + _strength: f64, + dim_name: &str, +) -> Result<(), Box> { + world + .dimensions + .iter_mut() + .find(|d| d.name == dim_name) + .ok_or_else(|| format!("Dimension '{}' not found", dim_name))?; + Ok(()) +} + +fn generate_layers_fn(world: &mut World, dim_name: &str) -> Result<(), Box> { + let dim = world + .dimensions + .iter_mut() + .find(|d| d.name == dim_name) + .ok_or_else(|| format!("Dimension '{}' not found", dim_name))?; + for (_, tile) in &mut dim.tiles { + generate_all_layers(tile, world.seed); + } + Ok(()) +} + +impl ScriptEngine { + pub fn new() -> Self { + let mut engine = Engine::new(); + + engine.register_type::(); + + engine.register_fn("new_world", new_world_fn); + engine.register_fn("save_world", save_world_fn); + engine.register_fn("info", info_fn); + engine.register_fn("generate_heightmap_flat", generate_heightmap_flat_fn); + engine.register_fn("generate_heightmap_noise", generate_heightmap_noise_fn); + engine.register_fn("apply_smooth", apply_smooth_fn); + engine.register_fn("apply_erode", apply_erode_fn); + engine.register_fn("generate_layers", generate_layers_fn); + + Self { engine } + } + + pub fn run(&mut self, script: &str) -> Result> { + let ast = self.engine.compile(script)?; + self.engine.eval_ast(&ast) + } +} diff --git a/crates/scripting/tests/integration_test.rs b/crates/scripting/tests/integration_test.rs new file mode 100644 index 0000000..e500abe --- /dev/null +++ b/crates/scripting/tests/integration_test.rs @@ -0,0 +1,56 @@ +use std::path::Path; + +use terrafier_core::World; +use terrafier_scripting::ScriptEngine; + +#[test] +fn test_new_world() { + let mut engine = ScriptEngine::new(); + let result = engine + .run( + r#" + let world = new_world("test_world", 12345); + let i = info(world); + i.contains("test_world") && i.contains("12345") + "#, + ) + .unwrap(); + assert!(result.as_bool().unwrap()); +} + +#[test] +fn test_generate_flat_heightmap() { + let mut engine = ScriptEngine::new(); + let _ = engine + .run( + r#" + let world = new_world("flat_world", 42); + generate_heightmap_flat(world, 64, "overworld"); + save_world(world, "/tmp/test_flat.tfw"); + "#, + ) + .unwrap(); + let loaded: World = terrafier_core::io::load_binary(Path::new("/tmp/test_flat.tfw")).unwrap(); + if let Some(dim) = loaded.dimensions.first() { + for (_, tile) in &dim.tiles { + for z in 0..128 { + for x in 0..128 { + assert_eq!(tile.get_height(x, z), 64); + } + } + } + } + let _ = std::fs::remove_file("/tmp/test_flat.tfw"); +} + +#[test] +fn test_script_error_reporting() { + let mut engine = ScriptEngine::new(); + let result = engine.run( + r#" + let world = new_world("bad", 1); + generate_heightmap_flat(world, 64, "nonexistent"); + "#, + ); + assert!(result.is_err()); +} diff --git a/crates/wasm-host/Cargo.toml b/crates/wasm-host/Cargo.toml new file mode 100644 index 0000000..dd4603e --- /dev/null +++ b/crates/wasm-host/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "terrafier-wasm-host" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "WASM plugin host for Terrafier using wasmtime" + +[dependencies] +wasmtime = "46" +terrafier-core = { path = "../../core" } +anyhow.workspace = true +thiserror.workspace = true +log.workspace = true + +[dev-dependencies] +tempfile = "3" +wat = "1" diff --git a/crates/wasm-host/src/context.rs b/crates/wasm-host/src/context.rs new file mode 100644 index 0000000..10180e0 --- /dev/null +++ b/crates/wasm-host/src/context.rs @@ -0,0 +1,12 @@ +use terrafier_core::model::tile::Tile; + +pub struct WorldContext { + pub tile: Tile, + pub seed: u64, + pub tile_x: i32, + pub tile_z: i32, + pub hm_offset: usize, + pub ter_offset: usize, + pub wat_offset: usize, + pub error: Option, +} diff --git a/crates/wasm-host/src/error.rs b/crates/wasm-host/src/error.rs new file mode 100644 index 0000000..76f8a4d --- /dev/null +++ b/crates/wasm-host/src/error.rs @@ -0,0 +1,13 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum WasmHostError { + #[error("Failed to load WASM module: {0}")] + LoadError(#[from] anyhow::Error), + #[error("WASM plugin does not export required function: {0}")] + MissingExport(String), + #[error("WASM plugin returned error: {0}")] + PluginError(String), + #[error("Memory access error: {0}")] + MemoryError(String), +} diff --git a/crates/wasm-host/src/host.rs b/crates/wasm-host/src/host.rs new file mode 100644 index 0000000..5761780 --- /dev/null +++ b/crates/wasm-host/src/host.rs @@ -0,0 +1,195 @@ +use wasmtime::{Caller, Linker}; + +use crate::context::WorldContext; + +const TILE_SIZE: usize = 128; +const HEIGHTMAP_BYTES: usize = TILE_SIZE * TILE_SIZE * 2; +const TERRAIN_BYTES: usize = TILE_SIZE * TILE_SIZE; +const WATER_BYTES: usize = TILE_SIZE * TILE_SIZE; +const TOTAL_BUFFER: usize = HEIGHTMAP_BYTES + TERRAIN_BYTES + WATER_BYTES; + +pub fn register_host_functions(linker: &mut Linker) -> anyhow::Result<()> { + linker.func_wrap("env", "host_log", host_log)?; + linker.func_wrap("env", "host_get_seed", host_get_seed)?; + linker.func_wrap("env", "host_tile_size", host_tile_size)?; + + linker.func_wrap("env", "host_get_height", host_get_height)?; + linker.func_wrap("env", "host_set_height", host_set_height)?; + linker.func_wrap("env", "host_get_terrain", host_get_terrain)?; + linker.func_wrap("env", "host_set_terrain", host_set_terrain)?; + linker.func_wrap("env", "host_get_water", host_get_water)?; + linker.func_wrap("env", "host_set_water", host_set_water)?; + + linker.func_wrap("env", "host_alloc_buffers", host_alloc_buffers)?; + linker.func_wrap("env", "host_load_heightmap", host_load_heightmap)?; + linker.func_wrap("env", "host_load_terrain", host_load_terrain)?; + linker.func_wrap("env", "host_load_water", host_load_water)?; + linker.func_wrap("env", "host_store_heightmap", host_store_heightmap)?; + linker.func_wrap("env", "host_store_terrain", host_store_terrain)?; + linker.func_wrap("env", "host_store_water", host_store_water)?; + Ok(()) +} + +fn host_log(mut caller: Caller<'_, WorldContext>, level: i32, ptr: i32, len: i32) { + let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) else { + return; + }; + let mut buf = vec![0u8; len as usize]; + let _ = mem.read(&caller, ptr as usize, &mut buf); + let msg = String::from_utf8_lossy(&buf); + match level { + 0 => log::error!("[wasm] {}", msg), + 1 => log::warn!("[wasm] {}", msg), + 2 => log::info!("[wasm] {}", msg), + _ => log::debug!("[wasm] {}", msg), + } +} + +fn host_get_seed(caller: Caller<'_, WorldContext>) -> i64 { + let (lo, hi) = ( + (caller.data().seed & 0xFFFF_FFFF) as i64, + ((caller.data().seed >> 32) & 0xFFFF_FFFF) as i64, + ); + lo | (hi << 32) +} + +fn host_tile_size(_caller: Caller<'_, WorldContext>) -> i32 { + TILE_SIZE as i32 +} + +fn host_get_height(caller: Caller<'_, WorldContext>, x: i32, z: i32) -> i32 { + if x < 0 || z < 0 || x >= TILE_SIZE as i32 || z >= TILE_SIZE as i32 { + return 0; + } + caller.data().tile.get_height(x as usize, z as usize) as i32 +} + +fn host_set_height(mut caller: Caller<'_, WorldContext>, x: i32, z: i32, val: i32) { + if x < 0 || z < 0 || x >= TILE_SIZE as i32 || z >= TILE_SIZE as i32 { + return; + } + caller + .data_mut() + .tile + .set_height(x as usize, z as usize, val as i16); +} + +fn host_get_terrain(caller: Caller<'_, WorldContext>, x: i32, z: i32) -> i32 { + if x < 0 || z < 0 || x >= TILE_SIZE as i32 || z >= TILE_SIZE as i32 { + return 0; + } + caller.data().tile.get_terrain(x as usize, z as usize) as i32 +} + +fn host_set_terrain(mut caller: Caller<'_, WorldContext>, x: i32, z: i32, val: i32) { + if x < 0 || z < 0 || x >= TILE_SIZE as i32 || z >= TILE_SIZE as i32 { + return; + } + caller + .data_mut() + .tile + .set_terrain(x as usize, z as usize, val as u8); +} + +fn host_get_water(caller: Caller<'_, WorldContext>, x: i32, z: i32) -> i32 { + if x < 0 || z < 0 || x >= TILE_SIZE as i32 || z >= TILE_SIZE as i32 { + return 0; + } + caller.data().tile.water_level[(z as usize) * TILE_SIZE + (x as usize)] as i32 +} + +fn host_set_water(mut caller: Caller<'_, WorldContext>, x: i32, z: i32, val: i32) { + if x < 0 || z < 0 || x >= TILE_SIZE as i32 || z >= TILE_SIZE as i32 { + return; + } + caller.data_mut().tile.water_level[(z as usize) * TILE_SIZE + (x as usize)] = val as u8; +} + +fn host_alloc_buffers(mut caller: Caller<'_, WorldContext>) -> i32 { + let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) else { + return -1; + }; + let needed_bytes = TOTAL_BUFFER; + let current_size = mem.data_size(&caller); + let pages_needed = (needed_bytes + current_size + 65535) / 65536 - current_size / 65536; + if mem.grow(&mut caller, pages_needed as u64).is_err() { + return -1; + } + let hm_offset = current_size; + let ter_offset = hm_offset + HEIGHTMAP_BYTES; + let wat_offset = ter_offset + TERRAIN_BYTES; + let ctx = caller.data_mut(); + ctx.hm_offset = hm_offset; + ctx.ter_offset = ter_offset; + ctx.wat_offset = wat_offset; + 0 +} + +fn host_load_heightmap(mut caller: Caller<'_, WorldContext>) -> i32 { + let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) else { + return -1; + }; + let offset = caller.data().hm_offset; + let bytes: Vec = caller + .data() + .tile + .heightmap + .iter() + .flat_map(|h| h.to_le_bytes()) + .collect(); + let _ = mem.write(&mut caller, offset, &bytes); + offset as i32 +} + +fn host_load_terrain(mut caller: Caller<'_, WorldContext>) -> i32 { + let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) else { + return -1; + }; + let offset = caller.data().ter_offset; + let terrain = caller.data().tile.terrain; + let _ = mem.write(&mut caller, offset, &terrain); + offset as i32 +} + +fn host_load_water(mut caller: Caller<'_, WorldContext>) -> i32 { + let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) else { + return -1; + }; + let offset = caller.data().wat_offset; + let water = caller.data().tile.water_level; + let _ = mem.write(&mut caller, offset, &water); + offset as i32 +} + +fn host_store_heightmap(mut caller: Caller<'_, WorldContext>) { + let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) else { + return; + }; + let offset = caller.data().hm_offset; + let mut buf = vec![0u8; HEIGHTMAP_BYTES]; + let _ = mem.read(&caller, offset, &mut buf); + let ctx = caller.data_mut(); + for (i, chunk) in buf.chunks_exact(2).enumerate() { + ctx.tile.heightmap[i] = i16::from_le_bytes([chunk[0], chunk[1]]); + } +} + +fn host_store_terrain(mut caller: Caller<'_, WorldContext>) { + let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) else { + return; + }; + let offset = caller.data().ter_offset; + let mut buf = [0u8; TERRAIN_BYTES]; + let _ = mem.read(&caller, offset, &mut buf); + caller.data_mut().tile.terrain = buf; +} + +fn host_store_water(mut caller: Caller<'_, WorldContext>) { + let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) else { + return; + }; + let offset = caller.data().wat_offset; + let mut buf = [0u8; WATER_BYTES]; + let _ = mem.read(&caller, offset, &mut buf); + caller.data_mut().tile.water_level = buf; +} diff --git a/crates/wasm-host/src/lib.rs b/crates/wasm-host/src/lib.rs new file mode 100644 index 0000000..aa39150 --- /dev/null +++ b/crates/wasm-host/src/lib.rs @@ -0,0 +1,43 @@ +mod context; +mod error; +mod host; +mod plugin; + +use std::path::Path; +use terrafier_core::plugins::PluginRegistry; + +pub use context::WorldContext; +pub use error::WasmHostError; +pub use plugin::{WasmPlugin, WasmTileSourcePlugin}; + +/// Load all .wasm plugins from a directory into the registry. +pub fn load_wasm_dir( + registry: &mut PluginRegistry, + dir: &Path, +) -> Result> { + let mut count = 0; + if dir.is_dir() { + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.extension().map(|e| e == "wasm").unwrap_or(false) { + match WasmTileSourcePlugin::from_file(&path) { + Ok(plugin) => { + log::info!( + "Loaded WASM plugin: {} v{} ({})", + plugin.name(), + plugin.version(), + path.display() + ); + registry.sources.push(Box::new(plugin)); + count += 1; + } + Err(e) => { + log::error!("Failed to load WASM plugin {}: {}", path.display(), e); + } + } + } + } + } + Ok(count) +} diff --git a/crates/wasm-host/src/plugin.rs b/crates/wasm-host/src/plugin.rs new file mode 100644 index 0000000..628b688 --- /dev/null +++ b/crates/wasm-host/src/plugin.rs @@ -0,0 +1,187 @@ +use std::path::Path; + +use terrafier_core::model::tile::Tile; +use terrafier_core::plugins::TileSourcePlugin; +use wasmtime::*; + +use crate::context::WorldContext; +use crate::error::WasmHostError; +use crate::host::register_host_functions; + +pub trait WasmPlugin { + fn name(&self) -> &str; + fn version(&self) -> &str; + fn plugin_type(&self) -> u32; +} + +pub struct WasmTileSourcePlugin { + engine: Engine, + module: Module, + name: String, + version: String, +} + +impl WasmTileSourcePlugin { + pub fn from_file(path: &Path) -> Result { + let engine = Engine::default(); + let wasm_bytes = std::fs::read(path).map_err(|e| { + WasmHostError::LoadError(anyhow::anyhow!("Failed to read .wasm file: {}", e)) + })?; + let module = Module::from_binary(&engine, &wasm_bytes) + .map_err(|e| WasmHostError::LoadError(anyhow::anyhow!("Invalid WASM module: {}", e)))?; + + let mut linker = Linker::new(&engine); + register_host_functions(&mut linker).map_err(|e| { + WasmHostError::LoadError(anyhow::anyhow!("Failed to register host functions: {}", e)) + })?; + let dummy_ctx = WorldContext { + tile: Tile::new(0, 0, -64, 320), + seed: 0, + tile_x: 0, + tile_z: 0, + hm_offset: 0, + ter_offset: 0, + wat_offset: 0, + error: None, + }; + let mut store = Store::new(&engine, dummy_ctx); + let instance = linker.instantiate(&mut store, &module).map_err(|e| { + WasmHostError::LoadError(anyhow::anyhow!("Failed to instantiate module: {}", e)) + })?; + + let memory = instance + .get_memory(&mut store, "memory") + .ok_or_else(|| WasmHostError::MissingExport("memory".into()))?; + + let name = Self::read_string(&mut store, &instance, &memory, "name_ptr", "name_len")?; + let version = + Self::read_string(&mut store, &instance, &memory, "version_ptr", "version_len")?; + + Ok(Self { + engine, + module, + name, + version, + }) + } + + fn read_string( + store: &mut Store, + instance: &Instance, + memory: &Memory, + ptr_func: &str, + len_func: &str, + ) -> Result { + let ptr = instance + .get_typed_func::<(), i32>(&mut *store, ptr_func) + .map_err(|_| WasmHostError::MissingExport(ptr_func.into()))? + .call(&mut *store, ()) + .map_err(|e| WasmHostError::PluginError(e.to_string()))?; + let len = instance + .get_typed_func::<(), i32>(&mut *store, len_func) + .map_err(|_| WasmHostError::MissingExport(len_func.into()))? + .call(&mut *store, ()) + .map_err(|e| WasmHostError::PluginError(e.to_string()))?; + + let mut buf = vec![0u8; len as usize]; + memory + .read(&*store, ptr as usize, &mut buf) + .map_err(|e| WasmHostError::MemoryError(e.to_string()))?; + Ok(String::from_utf8_lossy(&buf).to_string()) + } + + fn make_instance( + &self, + tile: &Tile, + seed: u64, + tile_x: i32, + tile_z: i32, + ) -> Result<(Store, Instance), WasmHostError> { + let mut linker = Linker::new(&self.engine); + register_host_functions(&mut linker).map_err(|e| WasmHostError::LoadError(e.into()))?; + + let ctx = WorldContext { + tile: tile.clone(), + seed, + tile_x, + tile_z, + hm_offset: 0, + ter_offset: 0, + wat_offset: 0, + error: None, + }; + let mut store = Store::new(&self.engine, ctx); + let instance = linker + .instantiate(&mut store, &self.module) + .map_err(|e| WasmHostError::LoadError(e.into()))?; + Ok((store, instance)) + } +} + +impl WasmPlugin for WasmTileSourcePlugin { + fn name(&self) -> &str { + &self.name + } + fn version(&self) -> &str { + &self.version + } + fn plugin_type(&self) -> u32 { + 3 + } +} + +impl TileSourcePlugin for WasmTileSourcePlugin { + fn name(&self) -> &'static str { + Box::leak(self.name.clone().into_boxed_str()) + } + + fn version(&self) -> &'static str { + Box::leak(self.version.clone().into_boxed_str()) + } + + fn generate_tile(&self, tile: &mut Tile, seed: u64) { + let tx = tile.x; + let tz = tile.z; + let tile_clone = tile.clone(); + let (mut store, instance) = match self.make_instance(&tile_clone, seed, tx, tz) { + Ok(v) => v, + Err(e) => { + log::error!( + "Failed to create WASM instance for plugin '{}': {}", + self.name, + e + ); + return; + } + }; + + let Ok(gen_func) = + instance.get_typed_func::<(i32, i32, i32, i32), i32>(&mut store, "generate_tile") + else { + log::error!( + "WASM plugin '{}' does not export 'generate_tile'", + self.name + ); + return; + }; + + let seed_lo = (seed & 0xFFFF_FFFF) as i32; + let seed_hi = ((seed >> 32) & 0xFFFF_FFFF) as i32; + + match gen_func.call(&mut store, (tx, tz, seed_lo, seed_hi)) { + Ok(0) => { + *tile = store.data().tile.clone(); + } + Ok(code) => { + log::error!( + "WASM plugin '{}' generate_tile returned error code: {}", + self.name, + code + ); + } + Err(e) => { + log::error!("WASM plugin '{}' generate_tile trap: {}", self.name, e); + } + } + } +} diff --git a/crates/wasm-host/tests/integration_test.rs b/crates/wasm-host/tests/integration_test.rs new file mode 100644 index 0000000..615a45f --- /dev/null +++ b/crates/wasm-host/tests/integration_test.rs @@ -0,0 +1,101 @@ +use terrafier_core::model::tile::Tile; +use terrafier_core::plugins::{PluginRegistry, TileSourcePlugin}; +use terrafier_wasm_host::{WasmTileSourcePlugin, load_wasm_dir}; + +const MINIMAL_PLUGIN_WAT: &str = r#" +(module + (import "env" "host_set_height" (func $host_set_height (param i32 i32 i32))) + (import "env" "host_set_terrain" (func $host_set_terrain (param i32 i32 i32))) + + (memory (export "memory") 1) + + (data (i32.const 1024) "test_plugin") + (data (i32.const 1040) "1.0.0") + + (func (export "name_ptr") (result i32) i32.const 1024) + (func (export "name_len") (result i32) i32.const 11) + (func (export "version_ptr") (result i32) i32.const 1040) + (func (export "version_len") (result i32) i32.const 5) + (func (export "plugin_type") (result i32) i32.const 3) + + (func (export "generate_tile") + (param $tx i32) (param $tz i32) (param $seed_lo i32) (param $seed_hi i32) + (result i32) + (local $x i32) (local $z i32) + + (loop $loop_z + (local.set $x (i32.const 0)) + (loop $loop_x + (call $host_set_height (local.get $x) (local.get $z) + (i32.add (local.get $x) (local.get $z))) + (call $host_set_terrain (local.get $x) (local.get $z) (i32.const 1)) + (br_if $loop_x + (i32.lt_u + (local.tee $x (i32.add (local.get $x) (i32.const 1))) + (i32.const 128))) + ) + (br_if $loop_z + (i32.lt_u + (local.tee $z (i32.add (local.get $z) (i32.const 1))) + (i32.const 128))) + ) + (i32.const 0) + ) +) +"#; + +#[test] +fn test_load_plugin_from_file() { + let dir = tempfile::tempdir().unwrap(); + let wasm_path = dir.path().join("test_plugin.wasm"); + let wasm_bytes = wat::parse_str(MINIMAL_PLUGIN_WAT).expect("Failed to parse WAT"); + std::fs::write(&wasm_path, &wasm_bytes).unwrap(); + + let plugin = WasmTileSourcePlugin::from_file(&wasm_path).expect("Failed to load WASM plugin"); + + assert_eq!(plugin.name(), "test_plugin"); + assert_eq!(plugin.version(), "1.0.0"); +} + +#[test] +fn test_generate_tile() { + let dir = tempfile::tempdir().unwrap(); + let wasm_path = dir.path().join("test_plugin.wasm"); + let wasm_bytes = wat::parse_str(MINIMAL_PLUGIN_WAT).expect("Failed to parse WAT"); + std::fs::write(&wasm_path, &wasm_bytes).unwrap(); + + let plugin = WasmTileSourcePlugin::from_file(&wasm_path).unwrap(); + let mut tile = Tile::new(5, 3, -64, 320); + + plugin.generate_tile(&mut tile, 42); + + assert_eq!(tile.x, 5); + assert_eq!(tile.z, 3); + + // Plugin sets height = x + z, terrain = 1 + for z in 0..128 { + for x in 0..128 { + assert_eq!(tile.get_height(x, z), (x + z) as i16); + assert_eq!(tile.get_terrain(x, z), 1); + } + } +} + +#[test] +fn test_load_wasm_dir() { + let dir = tempfile::tempdir().unwrap(); + + // Create two .wasm files + let wasm_bytes = wat::parse_str(MINIMAL_PLUGIN_WAT).expect("Failed to parse WAT"); + std::fs::write(dir.path().join("plugin_a.wasm"), &wasm_bytes).unwrap(); + std::fs::write(dir.path().join("plugin_b.wasm"), &wasm_bytes).unwrap(); + // Non-wasm file should be ignored + std::fs::write(dir.path().join("readme.txt"), b"ignore me").unwrap(); + + let mut registry = PluginRegistry::new(); + assert_eq!(registry.sources.len(), 0); + + let count = load_wasm_dir(&mut registry, dir.path()).unwrap(); + assert_eq!(count, 2); + assert_eq!(registry.sources.len(), 2); +} diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 4cf9223..065aadc 100755 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -3,8 +3,16 @@ name = "terrafier-gui" version.workspace = true edition.workspace = true license.workspace = true +repository.workspace = true +authors.workspace = true description = "Native GUI for Terrafier — interactive world painting with GPU rendering" +[package.metadata.wix] +upgrade-guid = "A7365100-6923-4DAC-B4A5-2BD817EB802B" +path-guid = "097E8AD4-180B-4B52-8BFE-04AA942D03A6" +license = false +eula = false + [dependencies] terrafier-core = { path = "../core" } anyhow.workspace = true diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index 3c0f01c..4c0dbe3 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -74,6 +74,8 @@ pub struct TerrafierApp { pub renderer: Option, pub show_gpu_render: bool, pub gpu_status: GpuStatus, + pub view_offset: egui::Vec2, + pub view_zoom: f32, pub cpu_texture_handle: Option, pub layer_visible: [bool; 6], } @@ -96,7 +98,10 @@ impl TerrafierApp { gpu_renderer.register_texture(&mut renderer_lock); (Some(gpu_renderer), GpuStatus::Enabled) } else { - (None, GpuStatus::FallbackCpu("GPU init failed — no wgpu adapter found")) + ( + None, + GpuStatus::FallbackCpu("GPU init failed — no wgpu adapter found"), + ) }; Self { @@ -124,9 +129,10 @@ impl TerrafierApp { renderer, show_gpu_render: true, gpu_status, + view_offset: egui::Vec2::ZERO, + view_zoom: 1.0, cpu_texture_handle: None, layer_visible: [false; 6], } } - } diff --git a/gui/src/app/ops.rs b/gui/src/app/ops.rs index 561f759..20f2080 100644 --- a/gui/src/app/ops.rs +++ b/gui/src/app/ops.rs @@ -14,16 +14,20 @@ impl TerrafierApp { pub fn undo(&mut self) { if let Some(op) = self.undo_stack.pop_back() { if let Some(ref mut world) = self.world { - let dim = world.overworld_mut().expect("world has no overworld"); - let inv = op.inverse(); - if let Err(e) = inv.apply(dim) { - self.status_message = format!("Undo error: {:?}", e); - self.undo_stack.push_back(op); + if let Some(dim) = world.overworld_mut() { + let inv = op.inverse(); + if let Err(e) = inv.apply(dim) { + self.status_message = format!("Undo error: {:?}", e); + self.undo_stack.push_back(op); + return; + } + self.redo_stack.push(op); + self.status_message = "Undo".to_string(); return; } - self.redo_stack.push(op); - self.status_message = "Undo".to_string(); } + self.undo_stack.push_back(op); + self.status_message = "Undo error: world has no overworld".to_string(); } else { self.status_message = "Nothing to undo".to_string(); } @@ -32,15 +36,19 @@ impl TerrafierApp { pub fn redo(&mut self) { if let Some(op) = self.redo_stack.pop() { if let Some(ref mut world) = self.world { - let dim = world.overworld_mut().expect("world has no overworld"); - if let Err(e) = op.apply(dim) { - self.status_message = format!("Redo error: {:?}", e); - self.redo_stack.push(op); + if let Some(dim) = world.overworld_mut() { + if let Err(e) = op.apply(dim) { + self.status_message = format!("Redo error: {:?}", e); + self.redo_stack.push(op); + return; + } + self.undo_stack.push_back(op); + self.status_message = "Redo".to_string(); return; } - self.undo_stack.push_back(op); - self.status_message = "Redo".to_string(); } + self.redo_stack.push(op); + self.status_message = "Redo error: world has no overworld".to_string(); } else { self.status_message = "Nothing to redo".to_string(); } diff --git a/gui/src/app/update.rs b/gui/src/app/update.rs index 67f248b..dd8ff8d 100644 --- a/gui/src/app/update.rs +++ b/gui/src/app/update.rs @@ -71,8 +71,8 @@ impl eframe::App for TerrafierApp { && let Some(tile) = world.dimensions[0].tiles.values().next() { renderer.render( - &tile.heightmap.as_slice().try_into().expect("heightmap size"), - &tile.terrain.as_slice().try_into().expect("terrain size"), + &tile.heightmap.as_slice().try_into().unwrap_or_else(|_| [0; 16384]), + &tile.terrain.as_slice().try_into().unwrap_or_else(|_| [0; 16384]), self.show_heightmap, ); diff --git a/gui/src/renderer/pipeline.rs b/gui/src/renderer/pipeline.rs index 7deb69b..814f732 100644 --- a/gui/src/renderer/pipeline.rs +++ b/gui/src/renderer/pipeline.rs @@ -47,57 +47,103 @@ impl GpuRenderer { needed_storage, ); } - let bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("terrain_bind_group_layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, count: None }, - wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: None }, count: None }, - wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: None }, count: None }, - wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: None }, count: None }, - ], - }); - let shader_module = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("terrain_shader"), - source: wgpu::ShaderSource::Wgsl(shader_source.into()), - }); - let pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("terrain_pipeline_layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - let compute_pipeline = - device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("terrain_compute_pipeline"), - layout: Some(&pipeline_layout), - module: &shader_module, - entry_point: Some("main"), - compilation_options: wgpu::PipelineCompilationOptions::default(), - cache: None, - }); + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("terrain_bind_group_layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("terrain_shader"), + source: wgpu::ShaderSource::Wgsl(shader_source.into()), + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("terrain_pipeline_layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("terrain_compute_pipeline"), + layout: Some(&pipeline_layout), + module: &shader_module, + entry_point: Some("main"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + cache: None, + }); let heightmap_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("heightmap_buffer"), size: 8192 * 4, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, + label: Some("heightmap_buffer"), + size: 8192 * 4, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, }); let terrain_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("terrain_buffer"), size: 4096 * 4, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, + label: Some("terrain_buffer"), + size: 4096 * 4, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, }); let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("uniform_buffer"), size: std::mem::size_of::() as u64, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, + label: Some("uniform_buffer"), + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, }); let output_size = (width * height * 4) as u64; let output_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("output_buffer"), size: output_size, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false, + label: Some("output_buffer"), + size: output_size, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, }); let output_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("output_texture"), - size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 }, - mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb], usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING, @@ -107,16 +153,54 @@ impl GpuRenderer { label: Some("terrain_bind_group"), layout: &bind_group_layout, entries: &[ - wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &uniform_buffer, offset: 0, size: None }) }, - wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &heightmap_buffer, offset: 0, size: None }) }, - wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &terrain_buffer, offset: 0, size: None }) }, - wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &output_buffer, offset: 0, size: None }) }, + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &uniform_buffer, + offset: 0, + size: None, + }), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &heightmap_buffer, + offset: 0, + size: None, + }), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &terrain_buffer, + offset: 0, + size: None, + }), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &output_buffer, + offset: 0, + size: None, + }), + }, ], }); Self { - device, queue, compute_pipeline, heightmap_buffer, terrain_buffer, - uniform_buffer, output_buffer, output_texture, output_texture_view, bind_group, - texture_id: None, width, height, + device, + queue, + compute_pipeline, + heightmap_buffer, + terrain_buffer, + uniform_buffer, + output_buffer, + output_texture, + output_texture_view, + bind_group, + texture_id: None, + width, + height, last_heightmap: vec![0i16; 16384], last_terrain: vec![0u8; 16384], last_show_heightmap: false, @@ -125,16 +209,25 @@ impl GpuRenderer { } pub(crate) fn pack_heightmap(heightmap: &[i16; 16384]) -> Vec { - heightmap.chunks(2).map(|chunk| { - let low = chunk[0] as u32 & 0xFFFF; - let high = chunk[1] as u32 & 0xFFFF; - low | (high << 16) - }).collect() + heightmap + .chunks(2) + .map(|chunk| { + let low = chunk[0] as u32 & 0xFFFF; + let high = chunk[1] as u32 & 0xFFFF; + low | (high << 16) + }) + .collect() } pub(crate) fn pack_terrain(terrain: &[u8; 16384]) -> Vec { - terrain.chunks(4).map(|chunk| { - chunk[0] as u32 | (chunk[1] as u32) << 8 | (chunk[2] as u32) << 16 | (chunk[3] as u32) << 24 - }).collect() + terrain + .chunks(4) + .map(|chunk| { + chunk[0] as u32 + | (chunk[1] as u32) << 8 + | (chunk[2] as u32) << 16 + | (chunk[3] as u32) << 24 + }) + .collect() } } diff --git a/gui/src/renderer/render.rs b/gui/src/renderer/render.rs index 05fbc9c..9588b53 100644 --- a/gui/src/renderer/render.rs +++ b/gui/src/renderer/render.rs @@ -2,21 +2,15 @@ use crate::renderer::GpuRenderer; use crate::renderer::Uniforms; fn as_u8_slice(data: &[T]) -> &[u8] { - unsafe { - std::slice::from_raw_parts( - data.as_ptr() as *const u8, - std::mem::size_of_val(data), - ) - } + // SAFETY: Reinterprets `&[T]` as `&[u8]` for GPU buffer upload. + // T is a primitive type with no padding, and the byte layout is stable. + unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) } } fn as_u8_value(data: &T) -> &[u8] { - unsafe { - std::slice::from_raw_parts( - data as *const T as *const u8, - std::mem::size_of::(), - ) - } + // SAFETY: Reinterprets a single `&T` as `&[u8]` for GPU buffer upload. + // T is a primitive type with no padding, and the byte layout is stable. + unsafe { std::slice::from_raw_parts(data as *const T as *const u8, std::mem::size_of::()) } } impl GpuRenderer { @@ -39,8 +33,8 @@ impl GpuRenderer { self.last_show_heightmap = show_heightmap; self.needs_upload = false; - let min_height = *heightmap.iter().min().unwrap(); - let max_height = *heightmap.iter().max().unwrap(); + let min_height = *heightmap.iter().min().unwrap_or(&0); + let max_height = *heightmap.iter().max().unwrap_or(&255); let packed_heightmap = Self::pack_heightmap(heightmap); self.queue diff --git a/gui/src/view/cpu.rs b/gui/src/view/cpu.rs index b161765..740c8e3 100644 --- a/gui/src/view/cpu.rs +++ b/gui/src/view/cpu.rs @@ -1,21 +1,45 @@ +use crate::app::{ + LAYER_BIOME_IDX, LAYER_CAVES_IDX, LAYER_FROST_IDX, LAYER_RESOURCES_IDX, LAYER_RIVER_IDX, + LAYER_TREES_IDX, +}; use egui::{Color32, ColorImage, TextureOptions, Vec2}; -use terrafier_core::model::tile::{TILE_SIZE, LayerBuffer}; -use terrafier_core::model::layers::{LAYER_CAVES, LAYER_RIVER, LAYER_FROST, LAYER_TREES, LAYER_BIOME, LAYER_RESOURCES}; -use crate::app::{LAYER_CAVES_IDX, LAYER_RIVER_IDX, LAYER_FROST_IDX, LAYER_TREES_IDX, LAYER_BIOME_IDX, LAYER_RESOURCES_IDX}; +use terrafier_core::model::layers::{ + LAYER_BIOME, LAYER_CAVES, LAYER_FROST, LAYER_RESOURCES, LAYER_RIVER, LAYER_TREES, +}; +use terrafier_core::model::tile::{LayerBuffer, TILE_SIZE}; -const LAYER_IDS: [u32; 6] = [LAYER_CAVES, LAYER_RIVER, LAYER_FROST, LAYER_TREES, LAYER_BIOME, LAYER_RESOURCES]; +const LAYER_IDS: [u32; 6] = [ + LAYER_CAVES, + LAYER_RIVER, + LAYER_FROST, + LAYER_TREES, + LAYER_BIOME, + LAYER_RESOURCES, +]; -fn layer_value(tile: &terrafier_core::model::tile::Tile, idx: usize, layer_idx: usize) -> Option { +fn layer_value( + tile: &terrafier_core::model::tile::Tile, + idx: usize, + layer_idx: usize, +) -> Option { let layer_id = LAYER_IDS[layer_idx]; let buf = tile.layer_data.get(&layer_id)?; let val = match buf { LayerBuffer::Bit(data) => { let word = data.get(idx / 64)?; - Some(if (word >> (idx % 64)) & 1 != 0 { 1.0 } else { 0.0 }) + Some(if (word >> (idx % 64)) & 1 != 0 { + 1.0 + } else { + 0.0 + }) } LayerBuffer::Nibble(data) => { let byte = *data.get(idx / 2)?; - let nib = if idx % 2 == 0 { byte & 0x0F } else { (byte >> 4) & 0x0F }; + let nib = if idx % 2 == 0 { + byte & 0x0F + } else { + (byte >> 4) & 0x0F + }; Some(nib as f32 / 15.0) } LayerBuffer::Byte(data) => { @@ -65,7 +89,9 @@ const TERRAIN_COLORS: [Color32; 7] = [ ]; pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) { - let Some(ref world) = app.world else { return; }; + let Some(ref world) = app.world else { + return; + }; let dim = &world.dimensions[0]; let mut min_tx = i32::MAX; @@ -79,20 +105,22 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) { max_tz = max_tz.max(tz); } - let display_size = 64u32; - let grid_w = ((max_tx - min_tx + 1) * display_size as i32) as usize; - let grid_h = ((max_tz - min_tz + 1) * display_size as i32) as usize; + let base_size = 64u32; + let zoom = app.view_zoom; + + let grid_w = ((max_tx - min_tx + 1) * base_size as i32) as usize; + let grid_h = ((max_tz - min_tz + 1) * base_size as i32) as usize; let mut img_data = vec![0u8; grid_w * grid_h * 4]; for (&(tx, tz), tile) in &dim.tiles { - let px = ((tx - min_tx) * display_size as i32) as usize; - let pz = ((tz - min_tz) * display_size as i32) as usize; + let px = ((tx - min_tx) * base_size as i32) as usize; + let pz = ((tz - min_tz) * base_size as i32) as usize; - for ly in 0..display_size as usize { - for lx in 0..display_size as usize { - let sx = (lx * TILE_SIZE / display_size as usize).min(TILE_SIZE - 1); - let sz = (ly * TILE_SIZE / display_size as usize).min(TILE_SIZE - 1); + for ly in 0..base_size as usize { + for lx in 0..base_size as usize { + let sx = (lx * TILE_SIZE / base_size as usize).min(TILE_SIZE - 1); + let sz = (ly * TILE_SIZE / base_size as usize).min(TILE_SIZE - 1); let idx = sz * TILE_SIZE + sx; let terrain_type = tile.terrain[idx] as usize; @@ -127,7 +155,11 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) { if app.layer_visible[li] { if let Some(val) = layer_value(tile, idx, li) { if let Some(over) = layer_overlay(li, val) { - let alpha = if val > 0.0 { (val * 0.6 + 0.2).min(0.8) } else { 0.0 }; + let alpha = if val > 0.0 { + (val * 0.6 + 0.2).min(0.8) + } else { + 0.0 + }; (r, g, b) = blend_overlay((r, g, b), over, alpha); } } @@ -156,13 +188,35 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) { id }; - let desired_size = Vec2::new(grid_w as f32, grid_h as f32); - let (response, painter) = ui.allocate_painter(desired_size, egui::Sense::click()); + // Viewport: allocate a large area for the world with scroll + let viewport_size = Vec2::new(grid_w as f32 * zoom, grid_h as f32 * zoom); + let (response, painter) = ui.allocate_painter( + viewport_size, + egui::Sense::click_and_drag(), + ); + + // Handle scroll-wheel zoom + let scroll_delta = ui.input(|i| i.raw_scroll_delta); + if scroll_delta.y != 0.0 { + app.view_zoom = (app.view_zoom * (1.0 + scroll_delta.y * 0.002)).clamp(0.1, 10.0); + } + + // Handle drag-to-pan + if response.dragged() { + app.view_offset += response.drag_delta(); + } + + // Recalculate scaled size after potential zoom change + let scaled_size = base_size as f32 * app.view_zoom; + let offset = app.view_offset; let rect = response.rect; painter.image( texture_id, - rect, + egui::Rect::from_min_max( + egui::pos2(rect.min.x + offset.x, rect.min.y + offset.y), + egui::pos2(rect.min.x + offset.x + grid_w as f32 * app.view_zoom, rect.min.y + offset.y + grid_h as f32 * app.view_zoom), + ), egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)), Color32::WHITE, ); @@ -173,10 +227,10 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) { && sel_tz >= min_tz && sel_tz <= max_tz { - let bx = rect.min.x + (sel_tx - min_tx) as f32 * display_size as f32; - let bz = rect.min.y + (sel_tz - min_tz) as f32 * display_size as f32; + let bx = rect.min.x + offset.x + (sel_tx - min_tx) as f32 * scaled_size; + let bz = rect.min.y + offset.y + (sel_tz - min_tz) as f32 * scaled_size; let border_rect = - egui::Rect::from_min_size(egui::pos2(bx, bz), Vec2::splat(display_size as f32)); + egui::Rect::from_min_size(egui::pos2(bx, bz), Vec2::splat(scaled_size)); painter.rect_stroke( border_rect, 0.0, @@ -188,13 +242,13 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) { if let (Some((sel_tx, sel_tz)), Some(bx), Some(bz)) = (app.selected_tile, app.brush_local_x, app.brush_local_z) { - let tile_x_in_pixels = (sel_tx - min_tx) as f32 * display_size as f32; - let tile_z_in_pixels = (sel_tz - min_tz) as f32 * display_size as f32; + let tile_x_in_pixels = (sel_tx - min_tx) as f32 * scaled_size; + let tile_z_in_pixels = (sel_tz - min_tz) as f32 * scaled_size; let brush_x = - rect.min.x + tile_x_in_pixels + (bx as f32 * display_size as f32 / TILE_SIZE as f32); + rect.min.x + offset.x + tile_x_in_pixels + (bx as f32 * scaled_size / TILE_SIZE as f32); let brush_z = - rect.min.y + tile_z_in_pixels + (bz as f32 * display_size as f32 / TILE_SIZE as f32); - let brush_radius_px = app.brush_radius as f32 * display_size as f32 / TILE_SIZE as f32; + rect.min.y + offset.y + tile_z_in_pixels + (bz as f32 * scaled_size / TILE_SIZE as f32); + let brush_radius_px = app.brush_radius as f32 * scaled_size / TILE_SIZE as f32; painter.circle_stroke( egui::pos2(brush_x, brush_z), brush_radius_px.max(2.0), @@ -202,20 +256,21 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) { ); } + // Click-to-select with inverse transform if response.clicked() && let Some(pos) = response.interact_pointer_pos() { - let lx = pos.x - rect.min.x; - let lz = pos.y - rect.min.y; + let lx = pos.x - rect.min.x - offset.x; + let lz = pos.y - rect.min.y - offset.y; if lx >= 0.0 && lz >= 0.0 { - let tx = (lx / display_size as f32).floor() as i32 + min_tx; - let tz = (lz / display_size as f32).floor() as i32 + min_tz; + let tx = (lx / scaled_size).floor() as i32 + min_tx; + let tz = (lz / scaled_size).floor() as i32 + min_tz; if dim.tiles.contains_key(&(tx, tz)) { app.selected_tile = Some((tx, tz)); let local_x = - ((lx as u32 % display_size) * TILE_SIZE as u32 / display_size).min(127); + ((lx as u32 % scaled_size as u32) * TILE_SIZE as u32 / scaled_size as u32).min(127); let local_z = - ((lz as u32 % display_size) * TILE_SIZE as u32 / display_size).min(127); + ((lz as u32 % scaled_size as u32) * TILE_SIZE as u32 / scaled_size as u32).min(127); app.brush_local_x = Some(local_x); app.brush_local_z = Some(local_z); app.status_message = format!( diff --git a/gui/src/view/gpu.rs b/gui/src/view/gpu.rs index 0b275cd..fd7c3f0 100644 --- a/gui/src/view/gpu.rs +++ b/gui/src/view/gpu.rs @@ -11,37 +11,64 @@ pub fn try_render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) -> bool && let Some(texture_id) = renderer.texture_id { let desired_size = Vec2::new(512.0, 512.0); - let (response, painter) = ui.allocate_painter(desired_size, egui::Sense::click()); + let (response, painter) = ui.allocate_painter( + desired_size, + egui::Sense::click_and_drag(), + ); let rect = response.rect; + + // Handle scroll-wheel zoom + let scroll_delta = ui.input(|i| i.raw_scroll_delta); + if scroll_delta.y != 0.0 { + app.view_zoom = (app.view_zoom * (1.0 + scroll_delta.y * 0.002)).clamp(0.1, 10.0); + } + + // Handle drag-to-pan + if response.dragged() { + app.view_offset += response.drag_delta(); + } + + let zoom = app.view_zoom; + let offset = app.view_offset; + + let base_display_size = rect.width() / 4.0; + let display_size = base_display_size * zoom; + + // Find tile bounds for positioning + let mut min_tx = i32::MAX; + let mut max_tx = i32::MIN; + let mut min_tz = i32::MAX; + let mut max_tz = i32::MIN; + for &(tx, tz) in dim.tiles.keys() { + min_tx = min_tx.min(tx); + max_tx = max_tx.max(tx); + min_tz = min_tz.min(tz); + max_tz = max_tz.max(tz); + } + + // Draw the GPU-rendered tile texture at the correct position + // GPU only renders the first tile - position it correctly + // Use a default position for the rendered tile + let first_tile_pos = dim.tiles.keys().next().copied().unwrap_or((0, 0)); + let tex_origin_x = rect.min.x + offset.x + (first_tile_pos.0 - min_tx) as f32 * display_size; + let tex_origin_y = rect.min.y + offset.y + (first_tile_pos.1 - min_tz) as f32 * display_size; + + let tex_rect = egui::Rect::from_min_size( + egui::pos2(tex_origin_x, tex_origin_y), + Vec2::splat(display_size), + ); painter.image( texture_id, - rect, + tex_rect, egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)), Color32::WHITE, ); - let display_size = rect.width() / 4.0; - if let Some((sel_tx, sel_tz)) = app.selected_tile { - let mut min_tx = i32::MAX; - let mut max_tx = i32::MIN; - let mut min_tz = i32::MAX; - let mut max_tz = i32::MIN; - for &(tx, tz) in dim.tiles.keys() { - min_tx = min_tx.min(tx); - max_tx = max_tx.max(tx); - min_tz = min_tz.min(tz); - max_tz = max_tz.max(tz); - } - - let bx = rect.min.x; - let bz = rect.min.y; - let sel_bx = bx + (sel_tx - min_tx) as f32 * display_size; - let sel_bz = bz + (sel_tz - min_tz) as f32 * display_size; - let border_rect = egui::Rect::from_min_size( - egui::pos2(sel_bx, sel_bz), - Vec2::splat(display_size), - ); + let bx = rect.min.x + offset.x + (sel_tx - min_tx) as f32 * display_size; + let bz = rect.min.y + offset.y + (sel_tz - min_tz) as f32 * display_size; + let border_rect = + egui::Rect::from_min_size(egui::pos2(bx, bz), Vec2::splat(display_size)); painter.rect_stroke( border_rect, 0.0, @@ -53,18 +80,12 @@ pub fn try_render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) -> bool if let (Some((sel_tx, sel_tz)), Some(bx), Some(bz)) = (app.selected_tile, app.brush_local_x, app.brush_local_z) { - let mut min_tx = i32::MAX; - let mut min_tz = i32::MAX; - for &(tx, tz) in dim.tiles.keys() { - min_tx = min_tx.min(tx); - min_tz = min_tz.min(tz); - } let tile_x_in_pixels = (sel_tx - min_tx) as f32 * display_size; let tile_z_in_pixels = (sel_tz - min_tz) as f32 * display_size; - let brush_x = rect.min.x + tile_x_in_pixels - + (bx as f32 * display_size / TILE_SIZE as f32); - let brush_z = rect.min.y + tile_z_in_pixels - + (bz as f32 * display_size / TILE_SIZE as f32); + let brush_x = + rect.min.x + offset.x + tile_x_in_pixels + (bx as f32 * display_size / TILE_SIZE as f32); + let brush_z = + rect.min.y + offset.y + tile_z_in_pixels + (bz as f32 * display_size / TILE_SIZE as f32); let brush_radius_px = app.brush_radius as f32 * display_size / TILE_SIZE as f32; painter.circle_stroke( egui::pos2(brush_x, brush_z), @@ -76,23 +97,19 @@ pub fn try_render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) -> bool if response.clicked() && let Some(pos) = response.interact_pointer_pos() { - let lx = pos.x - rect.min.x; - let lz = pos.y - rect.min.y; + let lx = pos.x - rect.min.x - offset.x; + let lz = pos.y - rect.min.y - offset.y; if lx >= 0.0 && lz >= 0.0 { - let mut min_tx = i32::MAX; - let mut min_tz = i32::MAX; - for &(tx, tz) in dim.tiles.keys() { - min_tx = min_tx.min(tx); - min_tz = min_tz.min(tz); - } let tx = (lx / display_size).floor() as i32 + min_tx; let tz = (lz / display_size).floor() as i32 + min_tz; if dim.tiles.contains_key(&(tx, tz)) { app.selected_tile = Some((tx, tz)); - let local_x = - ((lx as u32 % display_size as u32) * TILE_SIZE as u32 / display_size as u32).min(127); - let local_z = - ((lz as u32 % display_size as u32) * TILE_SIZE as u32 / display_size as u32).min(127); + let local_x = ((lx as u32 % display_size as u32) * TILE_SIZE as u32 + / display_size as u32) + .min(127); + let local_z = ((lz as u32 % display_size as u32) * TILE_SIZE as u32 + / display_size as u32) + .min(127); app.brush_local_x = Some(local_x); app.brush_local_z = Some(local_z); app.status_message = format!( diff --git a/gui/src/view/tools.rs b/gui/src/view/tools.rs index da66958..083dad6 100644 --- a/gui/src/view/tools.rs +++ b/gui/src/view/tools.rs @@ -1,14 +1,14 @@ use std::sync::Arc; use terrafier_core::Terrain; -use terrafier_core::model::types::SymmetricBrush; use terrafier_core::model::tile::TILE_SIZE; +use terrafier_core::model::types::SymmetricBrush; use terrafier_core::ops::operations::{ ErodeOperation, FillOperation, FlattenOperation, HeightOperation, MultiTileOperation, Operation, PaintOperation, SmoothOperation, }; -use crate::app::{TerrafierApp, ToolMode, LAYER_NAMES}; +use crate::app::{LAYER_NAMES, TerrafierApp, ToolMode}; pub fn show_tools_panel(ui: &mut egui::Ui, app: &mut TerrafierApp) { ui.heading("Tools"); @@ -83,7 +83,10 @@ fn apply_tool(app: &mut TerrafierApp) { let Some(ref mut world) = app.world else { return; }; - let dim = world.overworld_mut().expect("no overworld"); + let Some(dim) = world.overworld_mut() else { + app.status_message = "World has no overworld".to_string(); + return; + }; let center = app.brush_local_x.unwrap_or(64); let center_z = app.brush_local_z.unwrap_or(64); diff --git a/gui/wix/main.wxs b/gui/wix/main.wxs new file mode 100644 index 0000000..f00164e --- /dev/null +++ b/gui/wix/main.wxs @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + + + diff --git a/plugins/example-noisemap/Cargo.lock b/plugins/example-noisemap/Cargo.lock new file mode 100644 index 0000000..e523ef4 --- /dev/null +++ b/plugins/example-noisemap/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "terrafier-plugin-example-noisemap" +version = "0.1.0" diff --git a/plugins/example-noisemap/Cargo.toml b/plugins/example-noisemap/Cargo.toml new file mode 100644 index 0000000..b5b33cc --- /dev/null +++ b/plugins/example-noisemap/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "terrafier-plugin-example-noisemap" +version = "0.1.0" +edition = "2024" +description = "Example WASM plugin for Terrafier — generates noise-based terrain" + +[lib] +crate-type = ["cdylib"] + +[profile.release] +opt-level = "s" +lto = true diff --git a/plugins/example-noisemap/build.sh b/plugins/example-noisemap/build.sh new file mode 100755 index 0000000..98cd0f6 --- /dev/null +++ b/plugins/example-noisemap/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash +cd "$(dirname "$0")" +cargo build --target wasm32-unknown-unknown --release +echo "WASM plugin built: $(ls -lh target/wasm32-unknown-unknown/release/*.wasm 2>/dev/null || echo 'BUILD FAILED')" diff --git a/plugins/example-noisemap/src/lib.rs b/plugins/example-noisemap/src/lib.rs new file mode 100644 index 0000000..98de06b --- /dev/null +++ b/plugins/example-noisemap/src/lib.rs @@ -0,0 +1,216 @@ +#![no_std] + +// Allocator for WASM — bump allocator +// SAFETY: WASM is single-threaded; no concurrent access to BUMP is possible. +// The allocator is only called during plugin initialization before tile generation. +static mut BUMP: usize = 4096; // start after string data + +#[unsafe(no_mangle)] +pub extern "C" fn alloc(size: i32) -> i32 { + // SAFETY: WASM is single-threaded; no concurrent access to BUMP is possible. + // The allocator is only called during plugin initialization before tile generation. + unsafe { + let ptr = BUMP as i32; + BUMP += size as usize; + ptr + } +} + +// Host imports +// SAFETY: These host functions are guaranteed by the Terrafier WASM host ABI +// to be callable from within generate_tile. The host validates all coordinates +// (0..127) internally. All set functions are idempotent and panic-free. +unsafe extern "C" { + fn host_log(level: i32, ptr: i32, len: i32); + fn host_get_seed() -> i64; + fn host_tile_size() -> i32; + fn host_get_height(x: i32, z: i32) -> i32; + fn host_set_height(x: i32, z: i32, val: i32); + fn host_get_terrain(x: i32, z: i32) -> i32; + fn host_set_terrain(x: i32, z: i32, val: i32); + fn host_get_water(x: i32, z: i32) -> i32; + fn host_set_water(x: i32, z: i32, val: i32); + // Bulk transfer (faster) + fn host_alloc_buffers() -> i32; + fn host_load_heightmap() -> i32; + fn host_load_terrain() -> i32; + fn host_load_water() -> i32; + fn host_store_heightmap(); + fn host_store_terrain(); + fn host_store_water(); +} + +// Simple pseudo-random number generator (LCG) +// SAFETY: WASM execution is single-threaded. RNG_STATE is only accessed +// during tile generation which runs sequentially per tile. +static mut RNG_STATE: u64 = 0; + +// SAFETY: WASM execution is single-threaded. RNG_STATE is only accessed +// during tile generation which runs sequentially per tile. +unsafe fn rng() -> u32 { + // SAFETY: Same single-thread guarantee as RNG_STATE declaration. + unsafe { + RNG_STATE = RNG_STATE.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); + (RNG_STATE >> 32) as u32 + } +} + +unsafe fn rng_range(min: f64, max: f64) -> f64 { + // SAFETY: rng() only accesses RNG_STATE, which is safe under single-threaded WASM. + let r = unsafe { rng() } as f64 / u32::MAX as f64; + min + r * (max - min) +} + +// Simple 2D noise function (value noise) +unsafe fn noise_2d(x: f64, z: f64, seed: u64) -> f64 { + let xi = x as i32; + let zi = z as i32; + let xf = x - xi as f64; + let zf = z - zi as f64; + + fn hash(x: i32, z: i32, seed: u64) -> f64 { + let h = (x as u64).wrapping_mul(374761393) + .wrapping_add((z as u64).wrapping_mul(668265263)) + .wrapping_add(seed) + .wrapping_mul(1274126177); + ((h ^ (h >> 16)) as u32) as f64 / u32::MAX as f64 + } + + let n00 = hash(xi, zi, seed); + let n10 = hash(xi + 1, zi, seed); + let n01 = hash(xi, zi + 1, seed); + let n11 = hash(xi + 1, zi + 1, seed); + + let sx = xf * xf * (3.0 - 2.0 * xf); + let sz = zf * zf * (3.0 - 2.0 * zf); + + let nx0 = n00 + (n10 - n00) * sx; + let nx1 = n01 + (n11 - n01) * sx; + nx0 + (nx1 - nx0) * sz +} + +// Helper to log strings from WASM +// SAFETY: Address 0-1024 in WASM linear memory is reserved as a scratch buffer. +// The host guarantees this region is writable. The copy size is bounded by msg length (max 1024). +#[allow(invalid_null_arguments)] +unsafe fn wasm_log(level: i32, msg: &str) { + let bytes = msg.as_bytes(); + let ptr = 0usize; + let len = bytes.len().min(1024); + // SAFETY: Address 0 is a valid scratch buffer in WASM linear memory. + // host_log is guaranteed by the host ABI. + unsafe { + core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr as *mut u8, len); + host_log(level, ptr as i32, len as i32); + } +} + +// Plugin metadata +#[unsafe(no_mangle)] +pub extern "C" fn name_ptr() -> i32 { 1024 } + +#[unsafe(no_mangle)] +pub extern "C" fn name_len() -> i32 { 16 } + +#[unsafe(no_mangle)] +pub extern "C" fn version_ptr() -> i32 { 1040 } + +#[unsafe(no_mangle)] +pub extern "C" fn version_len() -> i32 { 5 } + +// Plugin type: 3 = TileSource +#[unsafe(no_mangle)] +pub extern "C" fn plugin_type() -> i32 { 3 } + +#[unsafe(no_mangle)] +pub extern "C" fn init() -> i32 { + let name = b"Example NoiseMap"; + let version = b"1.0.0"; + // SAFETY: Addresses 1024+ and 1040+ are reserved for plugin metadata strings. + // The source bytes are valid string literals with static lifetime. + // Destinations are within the first memory page (64KB) guaranteed by the host. + unsafe { + core::ptr::copy_nonoverlapping(name.as_ptr(), 1024 as *mut u8, name.len()); + core::ptr::copy_nonoverlapping(version.as_ptr(), 1040 as *mut u8, version.len()); + } + 0 +} + +/// Terrain types: 0=Desert, 1=Grass, 2=Forest, 3=Rock, 4=Sand, 5=Swamp, 6=Water +const TERRAIN_GRASS: u8 = 1; +const TERRAIN_FOREST: u8 = 2; +const TERRAIN_ROCK: u8 = 3; +const TERRAIN_WATER: u8 = 6; + +#[unsafe(no_mangle)] +pub extern "C" fn generate_tile(tile_x: i32, tile_z: i32, seed_lo: i32, seed_hi: i32) -> i32 { + let seed = (seed_lo as u64) | ((seed_hi as u64) << 32); + + // SAFETY: All host_* functions are guaranteed by the Terrafier WASM host ABI + // to be callable from within generate_tile. The host validates all coordinates + // (0..127) internally. host_set_* functions are idempotent and panic-free. + unsafe { + RNG_STATE = seed; + + let tile_size = host_tile_size(); + let scale = 16.0; + + let origin_x = (tile_x as f64) * (tile_size as f64); + let origin_z = (tile_z as f64) * (tile_size as f64); + + let water_level = 32; + + for tz in 0..tile_size { + for tx in 0..tile_size { + let wx = (tx as f64) + origin_x; + let wz = (tz as f64) + origin_z; + + let mut height: f64 = 0.0; + let mut amplitude = 1.0; + let mut frequency = 1.0; + let mut max_val = 0.0; + + for _octave in 0..6 { + let nx = wx / scale * frequency; + let nz = wz / scale * frequency; + height += noise_2d(nx, nz, seed.wrapping_add(_octave as u64)) * amplitude; + max_val += amplitude; + amplitude *= 0.5; + frequency *= 2.0; + } + + height /= max_val; + height = height * 80.0 + 20.0; + + let iheight = height as i32; + host_set_height(tx, tz, iheight); + + let terrain = if iheight < water_level + 2 { + TERRAIN_WATER + } else if iheight < water_level + 10 { + TERRAIN_GRASS + } else if iheight > 85 { + TERRAIN_ROCK + } else if rng_range(0.0, 1.0) < 0.3 { + TERRAIN_FOREST + } else { + TERRAIN_GRASS + }; + host_set_terrain(tx, tz, terrain as i32); + + let water = if iheight < water_level { + 8u8 + } else { + 0u8 + }; + host_set_water(tx, tz, water as i32); + } + } + } + 0 +} + +#[panic_handler] +fn panic_handler(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/plugins/example-noisemap/target/.rustc_info.json b/plugins/example-noisemap/target/.rustc_info.json new file mode 100644 index 0000000..2a06d95 --- /dev/null +++ b/plugins/example-noisemap/target/.rustc_info.json @@ -0,0 +1 @@ +{"rustc_fingerprint":7812569715578181117,"outputs":{"11652014622397750202":{"success":true,"status":"","code":0,"stdout":"___.wasm\nlib___.rlib\n___.wasm\nlib___.a\n/home/loki/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\n___\ndebug_assertions\npanic=\"abort\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"wasm32\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"wasm\"\ntarget_feature=\"bulk-memory\"\ntarget_feature=\"multivalue\"\ntarget_feature=\"mutable-globals\"\ntarget_feature=\"nontrapping-fptoint\"\ntarget_feature=\"reference-types\"\ntarget_feature=\"sign-ext\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"unknown\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"unknown\"\n","stderr":"warning: dropping unsupported crate type `dylib` for target `wasm32-unknown-unknown`\n\nwarning: dropping unsupported crate type `proc-macro` for target `wasm32-unknown-unknown`\n\nwarning: 2 warnings emitted\n\n"},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/loki/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"17181554143788415700":{"success":true,"status":"","code":0,"stdout":"rustc 1.96.1 (31fca3adb 2026-06-26)\nbinary: rustc\ncommit-hash: 31fca3adb283cc9dfd56b49cdee9a96eb9c96ffd\ncommit-date: 2026-06-26\nhost: x86_64-unknown-linux-gnu\nrelease: 1.96.1\nLLVM version: 22.1.2\n","stderr":""}},"successes":{}} \ No newline at end of file diff --git a/plugins/example-noisemap/target/CACHEDIR.TAG b/plugins/example-noisemap/target/CACHEDIR.TAG new file mode 100644 index 0000000..20d7c31 --- /dev/null +++ b/plugins/example-noisemap/target/CACHEDIR.TAG @@ -0,0 +1,3 @@ +Signature: 8a477f597d28d172789f06886806bc55 +# This file is a cache directory tag created by cargo. +# For information about cache directory tags see https://bford.info/cachedir/ diff --git a/plugins/example-noisemap/target/debug/.cargo-build-lock b/plugins/example-noisemap/target/debug/.cargo-build-lock new file mode 100644 index 0000000..e69de29 diff --git a/plugins/example-noisemap/target/debug/.cargo-lock b/plugins/example-noisemap/target/debug/.cargo-lock new file mode 100644 index 0000000..e69de29 diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/CACHEDIR.TAG b/plugins/example-noisemap/target/wasm32-unknown-unknown/CACHEDIR.TAG new file mode 100644 index 0000000..20d7c31 --- /dev/null +++ b/plugins/example-noisemap/target/wasm32-unknown-unknown/CACHEDIR.TAG @@ -0,0 +1,3 @@ +Signature: 8a477f597d28d172789f06886806bc55 +# This file is a cache directory tag created by cargo. +# For information about cache directory tags see https://bford.info/cachedir/ diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.cargo-build-lock b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.cargo-build-lock new file mode 100644 index 0000000..e69de29 diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.cargo-lock b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.cargo-lock new file mode 100644 index 0000000..e69de29 diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/dep-lib-terrafier_plugin_example_noisemap b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/dep-lib-terrafier_plugin_example_noisemap new file mode 100644 index 0000000..024be49 Binary files /dev/null and b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/dep-lib-terrafier_plugin_example_noisemap differ diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/invoked.timestamp b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/invoked.timestamp new file mode 100644 index 0000000..e00328d --- /dev/null +++ b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/lib-terrafier_plugin_example_noisemap b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/lib-terrafier_plugin_example_noisemap new file mode 100644 index 0000000..49dd5ae --- /dev/null +++ b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/lib-terrafier_plugin_example_noisemap @@ -0,0 +1 @@ +76d65f109d43013b \ No newline at end of file diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/lib-terrafier_plugin_example_noisemap.json b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/lib-terrafier_plugin_example_noisemap.json new file mode 100644 index 0000000..d8011a0 --- /dev/null +++ b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/lib-terrafier_plugin_example_noisemap.json @@ -0,0 +1 @@ +{"rustc":9777074978655410247,"features":"[]","declared_features":"[]","target":2668537824883689644,"profile":17672942494452627365,"path":10763286916239946207,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/dep-lib-terrafier_plugin_example_noisemap","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":14682669768258224367} \ No newline at end of file diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/output-lib-terrafier_plugin_example_noisemap b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/output-lib-terrafier_plugin_example_noisemap new file mode 100644 index 0000000..7f76776 --- /dev/null +++ b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/.fingerprint/terrafier-plugin-example-noisemap-14d72cbe84c58ee8/output-lib-terrafier_plugin_example_noisemap @@ -0,0 +1,14 @@ +{"$message_type":"diagnostic","message":"function `wasm_log` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":3450,"byte_end":3458,"line_start":96,"line_end":96,"column_start":11,"column_end":19,"is_primary":true,"text":[{"text":"unsafe fn wasm_log(level: i32, msg: &str) {","highlight_start":11,"highlight_end":19}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `wasm_log` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:96:11\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m96\u001b[0m \u001b[1m\u001b[94m|\u001b[0m unsafe fn wasm_log(level: i32, msg: &str) {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default\n\n"} +{"$message_type":"diagnostic","message":"function `host_log` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":881,"byte_end":889,"line_start":24,"line_end":24,"column_start":8,"column_end":16,"is_primary":true,"text":[{"text":" fn host_log(level: i32, ptr: i32, len: i32);","highlight_start":8,"highlight_end":16}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_log` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:24:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m24\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_log(level: i32, ptr: i32, len: i32);\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_get_seed` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":930,"byte_end":943,"line_start":25,"line_end":25,"column_start":8,"column_end":21,"is_primary":true,"text":[{"text":" fn host_get_seed() -> i64;","highlight_start":8,"highlight_end":21}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_get_seed` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:25:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m25\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_get_seed() -> i64;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_get_height` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":993,"byte_end":1008,"line_start":27,"line_end":27,"column_start":8,"column_end":23,"is_primary":true,"text":[{"text":" fn host_get_height(x: i32, z: i32) -> i32;","highlight_start":8,"highlight_end":23}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_get_height` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:27:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m27\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_get_height(x: i32, z: i32) -> i32;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_get_terrain` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1090,"byte_end":1106,"line_start":29,"line_end":29,"column_start":8,"column_end":24,"is_primary":true,"text":[{"text":" fn host_get_terrain(x: i32, z: i32) -> i32;","highlight_start":8,"highlight_end":24}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_get_terrain` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:29:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m29\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_get_terrain(x: i32, z: i32) -> i32;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_get_water` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1189,"byte_end":1203,"line_start":31,"line_end":31,"column_start":8,"column_end":22,"is_primary":true,"text":[{"text":" fn host_get_water(x: i32, z: i32) -> i32;","highlight_start":8,"highlight_end":22}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_get_water` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:31:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m31\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_get_water(x: i32, z: i32) -> i32;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_alloc_buffers` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1314,"byte_end":1332,"line_start":34,"line_end":34,"column_start":8,"column_end":26,"is_primary":true,"text":[{"text":" fn host_alloc_buffers() -> i32;","highlight_start":8,"highlight_end":26}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_alloc_buffers` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:34:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m34\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_alloc_buffers() -> i32;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_load_heightmap` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1350,"byte_end":1369,"line_start":35,"line_end":35,"column_start":8,"column_end":27,"is_primary":true,"text":[{"text":" fn host_load_heightmap() -> i32;","highlight_start":8,"highlight_end":27}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_load_heightmap` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:35:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m35\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_load_heightmap() -> i32;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_load_terrain` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1387,"byte_end":1404,"line_start":36,"line_end":36,"column_start":8,"column_end":25,"is_primary":true,"text":[{"text":" fn host_load_terrain() -> i32;","highlight_start":8,"highlight_end":25}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_load_terrain` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:36:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m36\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_load_terrain() -> i32;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_load_water` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1422,"byte_end":1437,"line_start":37,"line_end":37,"column_start":8,"column_end":23,"is_primary":true,"text":[{"text":" fn host_load_water() -> i32;","highlight_start":8,"highlight_end":23}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_load_water` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:37:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m37\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_load_water() -> i32;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_store_heightmap` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1455,"byte_end":1475,"line_start":38,"line_end":38,"column_start":8,"column_end":28,"is_primary":true,"text":[{"text":" fn host_store_heightmap();","highlight_start":8,"highlight_end":28}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_store_heightmap` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:38:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m38\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_store_heightmap();\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_store_terrain` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1486,"byte_end":1504,"line_start":39,"line_end":39,"column_start":8,"column_end":26,"is_primary":true,"text":[{"text":" fn host_store_terrain();","highlight_start":8,"highlight_end":26}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_store_terrain` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:39:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m39\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_store_terrain();\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"function `host_store_water` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1515,"byte_end":1531,"line_start":40,"line_end":40,"column_start":8,"column_end":24,"is_primary":true,"text":[{"text":" fn host_store_water();","highlight_start":8,"highlight_end":24}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: function `host_store_water` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/lib.rs:40:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m40\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn host_store_water();\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^\u001b[0m\n\n"} +{"$message_type":"diagnostic","message":"13 warnings emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: 13 warnings emitted\u001b[0m\n\n"} diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/deps/libterrafier_plugin_example_noisemap-14d72cbe84c58ee8.rmeta b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/deps/libterrafier_plugin_example_noisemap-14d72cbe84c58ee8.rmeta new file mode 100644 index 0000000..e69de29 diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/deps/terrafier_plugin_example_noisemap-14d72cbe84c58ee8.d b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/deps/terrafier_plugin_example_noisemap-14d72cbe84c58ee8.d new file mode 100644 index 0000000..a665ee2 --- /dev/null +++ b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/deps/terrafier_plugin_example_noisemap-14d72cbe84c58ee8.d @@ -0,0 +1,5 @@ +/storage/project/Terrafier/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/deps/terrafier_plugin_example_noisemap-14d72cbe84c58ee8.d: src/lib.rs + +/storage/project/Terrafier/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/deps/libterrafier_plugin_example_noisemap-14d72cbe84c58ee8.rmeta: src/lib.rs + +src/lib.rs: diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0-91gh4v9b9ie5o2bnlwcrqp175/dep-graph.bin b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0-91gh4v9b9ie5o2bnlwcrqp175/dep-graph.bin new file mode 100644 index 0000000..42068be Binary files /dev/null and b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0-91gh4v9b9ie5o2bnlwcrqp175/dep-graph.bin differ diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0-91gh4v9b9ie5o2bnlwcrqp175/query-cache.bin b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0-91gh4v9b9ie5o2bnlwcrqp175/query-cache.bin new file mode 100644 index 0000000..bc55552 Binary files /dev/null and b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0-91gh4v9b9ie5o2bnlwcrqp175/query-cache.bin differ diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0-91gh4v9b9ie5o2bnlwcrqp175/work-products.bin b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0-91gh4v9b9ie5o2bnlwcrqp175/work-products.bin new file mode 100644 index 0000000..00978c1 Binary files /dev/null and b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0-91gh4v9b9ie5o2bnlwcrqp175/work-products.bin differ diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0.lock b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27i4fig3-1sn51l0.lock new file mode 100644 index 0000000..e69de29 diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3-a5ky9mmjfvg1lji5jjcrqco2u/dep-graph.bin b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3-a5ky9mmjfvg1lji5jjcrqco2u/dep-graph.bin new file mode 100644 index 0000000..cfc43fb Binary files /dev/null and b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3-a5ky9mmjfvg1lji5jjcrqco2u/dep-graph.bin differ diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3-a5ky9mmjfvg1lji5jjcrqco2u/query-cache.bin b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3-a5ky9mmjfvg1lji5jjcrqco2u/query-cache.bin new file mode 100644 index 0000000..024f62b Binary files /dev/null and b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3-a5ky9mmjfvg1lji5jjcrqco2u/query-cache.bin differ diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3-a5ky9mmjfvg1lji5jjcrqco2u/work-products.bin b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3-a5ky9mmjfvg1lji5jjcrqco2u/work-products.bin new file mode 100644 index 0000000..00978c1 Binary files /dev/null and b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3-a5ky9mmjfvg1lji5jjcrqco2u/work-products.bin differ diff --git a/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3.lock b/plugins/example-noisemap/target/wasm32-unknown-unknown/debug/incremental/terrafier_plugin_example_noisemap-09gi7tw9ywzfb/s-hk27io9mtq-07w9ja3.lock new file mode 100644 index 0000000..e69de29 diff --git a/scripts/example.rhai b/scripts/example.rhai new file mode 100644 index 0000000..542377c --- /dev/null +++ b/scripts/example.rhai @@ -0,0 +1,18 @@ +// Terrafier script example — creates a world with noise terrain +let world = new_world("scripted_world", 42); + +// Generate noise terrain +generate_heightmap_noise(world, 12345, 16.0, 6, "overworld"); + +// Apply some operations +apply_smooth(world, 2, 5.0, "overworld"); +apply_erode(world, 1, 4.0, "overworld"); + +// Generate layers (caves, rivers, biomes, etc.) +generate_layers(world, "overworld"); + +// Save the world +save_world(world, "scripted_world.tfw"); + +print(info(world)); +print("Done! World saved to scripted_world.tfw");