diff --git a/.github/workflows/angry_birds.yml b/.github/workflows/angry_birds.yml index 85c5287..3475b59 100644 --- a/.github/workflows/angry_birds.yml +++ b/.github/workflows/angry_birds.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/angry_birds/**' + - '!examples/angry_birds/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/angry_birds.yml' pull_request: branches: [main, develop] paths: - 'examples/angry_birds/**' + - '!examples/angry_birds/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/angry_birds.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: angry-birds-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/angry_birds - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/angry_birds - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Angry Birds CI + working_directory: examples/angry_birds diff --git a/.github/workflows/arkanoid.yml b/.github/workflows/arkanoid.yml index 266097e..7e76d74 100644 --- a/.github/workflows/arkanoid.yml +++ b/.github/workflows/arkanoid.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/arkanoid/**' + - '!examples/arkanoid/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/arkanoid.yml' pull_request: branches: [main, develop] paths: - 'examples/arkanoid/**' + - '!examples/arkanoid/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/arkanoid.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: arkanoid-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/arkanoid - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/arkanoid - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Arkanoid CI + working_directory: examples/arkanoid diff --git a/.github/workflows/asteroids.yml b/.github/workflows/asteroids.yml index 792365c..173677d 100644 --- a/.github/workflows/asteroids.yml +++ b/.github/workflows/asteroids.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/asteroids/**' + - '!examples/asteroids/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/asteroids.yml' pull_request: branches: [main, develop] paths: - 'examples/asteroids/**' + - '!examples/asteroids/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/asteroids.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: asteroids-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/asteroids - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/asteroids - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Asteroids CI + working_directory: examples/asteroids diff --git a/.github/workflows/bomberman.yml b/.github/workflows/bomberman.yml index 9fd0944..d8cc5d7 100644 --- a/.github/workflows/bomberman.yml +++ b/.github/workflows/bomberman.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/bomberman/**' + - '!examples/bomberman/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/bomberman.yml' pull_request: branches: [main, develop] paths: - 'examples/bomberman/**' + - '!examples/bomberman/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/bomberman.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: bomberman-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/bomberman - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/bomberman - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Bomberman CI + working_directory: examples/bomberman diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 60eefd5..4e50b86 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -10,6 +10,7 @@ on: - 'Cargo.toml' - 'Cargo.lock' - '.github/workflows/core.yml' + - '!.github/**/*.md' pull_request: branches: [main, develop] paths: @@ -19,14 +20,20 @@ on: - 'Cargo.toml' - 'Cargo.lock' - '.github/workflows/core.yml' + - '!.github/**/*.md' env: CARGO_TERM_COLOR: always +concurrency: + group: core-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: core: name: Library Validation runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/example_reusable.yml b/.github/workflows/example_reusable.yml new file mode 100644 index 0000000..c9abd9a --- /dev/null +++ b/.github/workflows/example_reusable.yml @@ -0,0 +1,64 @@ +name: Example Reusable CI + +on: + workflow_call: + inputs: + job_name: + required: true + type: string + working_directory: + required: true + type: string + runs_on: + required: false + type: string + default: macos-latest + build_stellar_contract: + required: false + type: boolean + default: true + +env: + CARGO_TERM_COLOR: always + +jobs: + ci: + name: ${{ inputs.job_name }} + runs-on: ${{ inputs.runs_on }} + timeout-minutes: 20 + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32v1-none + components: rustfmt, clippy + + - name: Cache dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ inputs.working_directory }} + + - name: Check formatting + run: cargo fmt --check + + - name: Clippy + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: Build + run: cargo build + + - name: Test + run: cargo test + + - name: Install Stellar CLI + if: ${{ inputs.build_stellar_contract }} + run: brew install stellar-cli + + - name: Stellar contract build + if: ${{ inputs.build_stellar_contract }} + run: stellar contract build diff --git a/.github/workflows/flappy_bird.yml b/.github/workflows/flappy_bird.yml index ee06ecb..0a2a20a 100644 --- a/.github/workflows/flappy_bird.yml +++ b/.github/workflows/flappy_bird.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/flappy_bird/**' + - '!examples/flappy_bird/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/flappy_bird.yml' pull_request: branches: [main, develop] paths: - 'examples/flappy_bird/**' + - '!examples/flappy_bird/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/flappy_bird.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: flappy-bird-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/flappy_bird - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/flappy_bird - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Flappy Bird CI + working_directory: examples/flappy_bird diff --git a/.github/workflows/geometry_dash.yml b/.github/workflows/geometry_dash.yml index fdb692a..6b79c42 100644 --- a/.github/workflows/geometry_dash.yml +++ b/.github/workflows/geometry_dash.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/geometry_dash/**' + - '!examples/geometry_dash/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/geometry_dash.yml' pull_request: branches: [main, develop] paths: - 'examples/geometry_dash/**' + - '!examples/geometry_dash/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/geometry_dash.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: geometry-dash-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/geometry_dash - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/geometry_dash - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Geometry Dash CI + working_directory: examples/geometry_dash diff --git a/.github/workflows/guild_arena.yml b/.github/workflows/guild_arena.yml index 8b9ae9a..1f71838 100644 --- a/.github/workflows/guild_arena.yml +++ b/.github/workflows/guild_arena.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - "examples/guild_arena/**" + - "!examples/guild_arena/**/*.md" + - ".github/workflows/example_reusable.yml" - ".github/workflows/guild_arena.yml" pull_request: branches: [main, develop] paths: - "examples/guild_arena/**" + - "!examples/guild_arena/**/*.md" + - ".github/workflows/example_reusable.yml" - ".github/workflows/guild_arena.yml" -env: - CARGO_TERM_COLOR: always +concurrency: + group: guild-arena-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/guild_arena - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/guild_arena - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Guild Arena CI + working_directory: examples/guild_arena diff --git a/.github/workflows/pac_man.yml b/.github/workflows/pac_man.yml index e851507..216d713 100644 --- a/.github/workflows/pac_man.yml +++ b/.github/workflows/pac_man.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/pac_man/**' + - '!examples/pac_man/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/pac_man.yml' pull_request: branches: [main, develop] paths: - 'examples/pac_man/**' + - '!examples/pac_man/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/pac_man.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: pac-man-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/pac_man - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/pac_man - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Pac-Man CI + working_directory: examples/pac_man diff --git a/.github/workflows/pokemon_mini.yml b/.github/workflows/pokemon_mini.yml index f7e32e7..7282344 100644 --- a/.github/workflows/pokemon_mini.yml +++ b/.github/workflows/pokemon_mini.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/pokemon_mini/**' + - '!examples/pokemon_mini/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/pokemon_mini.yml' pull_request: branches: [main, develop] paths: - 'examples/pokemon_mini/**' + - '!examples/pokemon_mini/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/pokemon_mini.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: pokemon-mini-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/pokemon_mini - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/pokemon_mini - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Pokemon Mini CI + working_directory: examples/pokemon_mini diff --git a/.github/workflows/pong.yml b/.github/workflows/pong.yml index 4f0f408..445602d 100644 --- a/.github/workflows/pong.yml +++ b/.github/workflows/pong.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/pong/**' + - '!examples/pong/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/pong.yml' pull_request: branches: [main, develop] paths: - 'examples/pong/**' + - '!examples/pong/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/pong.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: pong-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/pong - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/pong - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Pong CI + working_directory: examples/pong diff --git a/.github/workflows/snake.yml b/.github/workflows/snake.yml index 07d738d..c7bb7ed 100644 --- a/.github/workflows/snake.yml +++ b/.github/workflows/snake.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/snake/**' + - '!examples/snake/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/snake.yml' pull_request: branches: [main, develop] paths: - 'examples/snake/**' + - '!examples/snake/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/snake.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: snake-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/snake - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/snake - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Snake CI + working_directory: examples/snake diff --git a/.github/workflows/space_invaders.yml b/.github/workflows/space_invaders.yml index aa2791e..afba3d9 100644 --- a/.github/workflows/space_invaders.yml +++ b/.github/workflows/space_invaders.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/space_invaders/**' + - '!examples/space_invaders/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/space_invaders.yml' pull_request: branches: [main, develop] paths: - 'examples/space_invaders/**' + - '!examples/space_invaders/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/space_invaders.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: space-invaders-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/space_invaders - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/space_invaders - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Space Invaders CI + working_directory: examples/space_invaders diff --git a/.github/workflows/tap_battle.yml b/.github/workflows/tap_battle.yml index 34efba6..973ab14 100644 --- a/.github/workflows/tap_battle.yml +++ b/.github/workflows/tap_battle.yml @@ -2,54 +2,27 @@ name: Tap Battle CI on: push: - branches: [ main, develop ] + branches: [main, develop] paths: - 'examples/tap_battle/**' + - '!examples/tap_battle/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/tap_battle.yml' pull_request: - branches: [ main, develop ] + branches: [main, develop] paths: - 'examples/tap_battle/**' + - '!examples/tap_battle/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/tap_battle.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: tap-battle-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - build: - runs-on: macos-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install Rust - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - source $HOME/.cargo/env - - name: Install Rust target for Soroban - run: | - source $HOME/.cargo/env - rustup target add wasm32v1-none - - name: Install Stellar CLI with Homebrew - run: | - brew update - brew install stellar-cli - stellar --version - - name: Build Cargo project - run: | - source $HOME/.cargo/env - cargo build --verbose - working-directory: examples/tap_battle - - - name: Build Soroban contract - run: | - source $HOME/.cargo/env - stellar contract build --verbose - working-directory: examples/tap_battle - - - name: Run Cargo tests - run: | - source $HOME/.cargo/env - cargo test --verbose - working-directory: examples/tap_battle + ci: + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Tap Battle CI + working_directory: examples/tap_battle diff --git a/.github/workflows/tetris.yml b/.github/workflows/tetris.yml index 95c683b..2e5871e 100644 --- a/.github/workflows/tetris.yml +++ b/.github/workflows/tetris.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/tetris/**' + - '!examples/tetris/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/tetris.yml' pull_request: branches: [main, develop] paths: - 'examples/tetris/**' + - '!examples/tetris/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/tetris.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: tetris-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/tetris - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/tetris - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Tetris CI + working_directory: examples/tetris diff --git a/.github/workflows/tic_tac_toe.yml b/.github/workflows/tic_tac_toe.yml index 220a8aa..512e0d5 100644 --- a/.github/workflows/tic_tac_toe.yml +++ b/.github/workflows/tic_tac_toe.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/tic_tac_toe/**' + - '!examples/tic_tac_toe/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/tic_tac_toe.yml' pull_request: branches: [main, develop] paths: - 'examples/tic_tac_toe/**' + - '!examples/tic_tac_toe/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/tic_tac_toe.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: tic-tac-toe-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/tic_tac_toe - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/tic_tac_toe - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Tic Tac Toe CI + working_directory: examples/tic_tac_toe diff --git a/.github/workflows/trading_card_game.yml b/.github/workflows/trading_card_game.yml index 719e4ed..497669e 100644 --- a/.github/workflows/trading_card_game.yml +++ b/.github/workflows/trading_card_game.yml @@ -5,51 +5,24 @@ on: branches: [main, develop] paths: - 'examples/trading_card_game/**' + - '!examples/trading_card_game/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/trading_card_game.yml' pull_request: branches: [main, develop] paths: - 'examples/trading_card_game/**' + - '!examples/trading_card_game/**/*.md' + - '.github/workflows/example_reusable.yml' - '.github/workflows/trading_card_game.yml' -env: - CARGO_TERM_COLOR: always +concurrency: + group: trading-card-game-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: ci: - name: CI - runs-on: macos-latest - defaults: - run: - working-directory: examples/trading_card_game - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32v1-none - components: rustfmt, clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: examples/trading_card_game - - - name: Check formatting - run: cargo fmt --check - - - name: Clippy - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build - - - name: Test - run: cargo test - - - name: Install Stellar CLI - run: brew install stellar-cli - - - name: Stellar contract build - run: stellar contract build + uses: ./.github/workflows/example_reusable.yml + with: + job_name: Trading Card Game CI + working_directory: examples/trading_card_game diff --git a/.gitignore b/.gitignore index 957f295..dd0a13d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ test_snapshots/ # Claude plans .claude/ +doocs/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e764027..8409460 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -8,8 +8,8 @@ High-level overview of how Cougr is organized. For usage, see [README.md](README ┌─────────────────────────────────────────────┐ │ GameWorld │ Unified API: ECS + Auth + ZK ├───────────┬───────────────┬─────────────────┤ -│ ECS │ Accounts │ ZK Proofs │ -├───────────┼───────────────┼─────────────────┤ +│ ECS │ Accounts │ Standards │ ZK Proofs +├───────────┼───────────────┼─────────────────┼─────────────────┤ │ soroban-sdk 25.1.0 (no_std, WASM) │ └─────────────────────────────────────────────┘ ``` @@ -72,6 +72,20 @@ Key traits: `CougrAccount`, `SessionKeyProvider`, `RecoveryProvider`, `MultiDevi `SessionBuilder` provides a fluent API for constructing scoped session keys. `authorize_with_fallback` handles graceful degradation from session keys to direct authorization. +## Standards (`src/standards/`) + +Reusable contract standards for integrations that need explicit operational controls: + +- `Ownable` and `Ownable2Step` for owner-managed authority +- `AccessControl` for role-based authorization with delegated admins +- `Pausable` for emergency stops +- `ExecutionGuard` for serialized critical sections +- `RecoveryGuard` for blocking sensitive paths during recovery windows +- `BatchExecutor` for bounded multi-operation flows +- `DelayedExecutionPolicy` for time-delayed operation queues + +Each standard instance is keyed by a caller-supplied `Symbol`, which keeps storage deterministic and avoids collisions when a contract composes multiple modules. + ## Feature Flags | Flag | Enables | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7663d8..dac5c94 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,3 +69,13 @@ Changes are more likely to be accepted when they: - include appropriate validation - improve the repository without increasing maintenance noise +## Public API Checklist + +Changes that touch public Rust APIs should be reviewed against this checklist before merge: + +- the symbol belongs to the curated onboarding path or an intentional namespace such as `accounts`, `zk::stable`, or `zk::experimental` +- stable, beta, experimental, and test-only surfaces are not mixed in the same default entrypoint +- new public names do not duplicate an existing public concept +- root-level re-exports are intentional and minimal +- examples and integration tests use the sanctioned public path instead of deep internal module paths +- documentation is updated to match the actual exported API diff --git a/Cargo.lock b/Cargo.lock index cc5a14b..e2bfbdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,7 +277,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cougr-core" -version = "0.0.1" +version = "0.5.0" dependencies = [ "soroban-sdk", "wee_alloc", diff --git a/Cargo.toml b/Cargo.toml index 40d3146..b8a58d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cougr-core" -version = "0.0.1" +version = "0.5.0" edition = "2021" description = "Cougr - A Soroban-compatible ECS framework for on-chain gaming on Stellar" license = "MIT" diff --git a/README.md b/README.md index c69a450..c15e573 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,20 @@ Cougr is a Rust framework for building on-chain games on Stellar with an Entity The repository includes the core library, a growing catalog of standalone game examples, and focused research notes for protocol and architecture work. The goal is to provide a practical foundation for teams building game logic that must remain structured, testable, and efficient under blockchain constraints. +## Project Status + +Cougr is still pre-`1.0`. The library has strong test coverage, but not every subsystem should be treated as equally mature. + +Current maturity baseline: + +| Area | Current status | +|---|---| +| ECS runtime and storage | Beta | +| Accounts and smart-account patterns | Beta | +| Standards layer (`standards`) | Beta | +| Privacy primitives (`zk::stable`) | Stable | +| Advanced ZK verification and confidential abstractions | Experimental | + ## What Cougr Provides | Area | What it includes | @@ -25,6 +39,7 @@ The repository includes the core library, a growing catalog of standalone game e | ECS | Typed components, multiple world implementations, scheduling, deferred commands, hooks, observers, and change tracking | | Zero-knowledge tooling | Groth16 verification, curve helpers, commitments, Merkle structures, reusable circuits, and ECS-integrated proof flows | | Smart account patterns | Session keys, social recovery, multi-device authorization, and fallback authorization flows | +| Contract standards | Ownable, Ownable2Step, AccessControl, Pausable, execution guards, recovery guards, delayed execution, and batch primitives | | Example contracts | 20+ example game projects and growing, each intended to show concrete patterns rather than isolated snippets | ## Quick Start @@ -35,8 +50,7 @@ cougr-core = { git = "https://github.com/salazarsebas/Cougr.git", branch = "main ``` ```rust -use cougr_core::component::Position; -use cougr_core::simple_world::SimpleWorld; +use cougr_core::{Position, SimpleWorld}; use soroban_sdk::Env; let env = Env::default(); @@ -79,6 +93,16 @@ let pos: Position = world.get_typed(&env, player).unwrap(); | Multi-device usage | Per-device policies and device-scoped access control | | Fallback authorization | Graceful degradation from advanced auth flows to direct authorization | +### Standards Layer + +| Capability | Description | +|---|---| +| `Ownable` and `Ownable2Step` | Owner-based authority with direct and staged transfers | +| `AccessControl` | Symbol-keyed roles with delegated role admins | +| `Pausable` | Emergency stop primitive for mutating entrypoints | +| `ExecutionGuard` and `RecoveryGuard` | Serialized critical sections and recovery-aware protection | +| `BatchExecutor` and `DelayedExecutionPolicy` | Bounded multi-operation flows and timelocked execution queues | + ## Example Projects The `examples/` directory contains 20+ standalone game contracts and is expected to keep growing. Examples are useful both as runnable references and as design patterns for structuring new projects on top of Cougr. @@ -119,6 +143,14 @@ Some examples also include Soroban-specific build flows using `stellar contract - [ARCHITECTURE.md](ARCHITECTURE.md) for the high-level organization of the framework - [examples/README.md](examples/README.md) for the example catalog and usage notes - [CONTRIBUTING.md](CONTRIBUTING.md) for contribution standards and workflow expectations +- [SECURITY.md](SECURITY.md) for the current security posture and reporting guidance +- [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md) for the current threat model and sensitive subsystem map +- [docs/MATURITY_MODEL.md](docs/MATURITY_MODEL.md) for maturity tiers and promotion criteria +- [docs/API_CONTRACT.md](docs/API_CONTRACT.md) for the current public API contract and compatibility boundaries +- [docs/ACCOUNT_KERNEL.md](docs/ACCOUNT_KERNEL.md) for the phase 1 account-kernel model, intents, signers, and replay protection +- [docs/PRIVACY_MODEL.md](docs/PRIVACY_MODEL.md) for the phase 2 privacy split, maturity table, and proof-verification contract +- [docs/STANDARDS_LAYER.md](docs/STANDARDS_LAYER.md) for the reusable standards layer, storage model, and failure semantics +- [docs/PUBLIC_GAPS.md](docs/PUBLIC_GAPS.md) for public behaviors that remain outside the stable promise ## Compatibility @@ -129,4 +161,3 @@ Some examples also include Soroban-specific build flows using `stellar contract | License | MIT | | Primary SDK | `soroban-sdk` 25.1.0 | | Targets | Soroban-compatible WASM targets | - diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..62a05f5 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,72 @@ +# Security Policy + +## Status + +Cougr is currently pre-`1.0` software. The repository contains stable ideas, but not every public subsystem should be treated as production-ready. + +Security-sensitive areas include: + +- account authorization +- session lifecycle and replay protection +- persistent storage integrity +- proof verification and privacy primitives +- ECS mutation ordering where authorization depends on state transitions + +## Maturity and Guarantees + +Current guidance: + +| Area | Status | Guidance | +|---|---|---| +| ECS runtime and storage | Beta | Suitable for active evaluation and internal use with validation | +| Accounts and smart-account flows | Beta | Do not assume full production guarantees without project-specific review | +| Privacy primitives | Beta | Commit-reveal and Merkle utilities are more mature than advanced proof flows | +| Advanced ZK verification | Experimental | Treat as non-stable until verification contracts and assumptions are fully hardened | + +The latest maturity definitions live in [docs/MATURITY_MODEL.md](docs/MATURITY_MODEL.md). +The current threat-model baseline lives in [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md). + +## Threat Model Expectations + +Cougr does not currently claim: + +- external audit coverage +- formal verification +- full production guarantees across all auth and privacy paths +- stable compatibility guarantees for experimental modules + +Before adopting Cougr in security-critical deployments, review at minimum: + +- auth and signer flows +- replay handling +- session scope and revocation rules +- storage schema assumptions +- proof verification assumptions + +## Reporting a Vulnerability + +If you find a security issue: + +1. Do not open a public issue with exploit details. +2. Report the issue privately to the project maintainers. +3. Include: + - affected module + - reproduction steps + - impact assessment + - version or commit information + - suggested mitigation if available + +Until a dedicated security contact is published, use the maintainer channels associated with this repository and clearly label the report as a security disclosure. + +## Supported Versions + +Because Cougr is pre-`1.0`, only the latest mainline development state should be assumed relevant for fixes unless a release branch explicitly says otherwise. + +## Secure Contribution Expectations + +Changes affecting auth, privacy, storage, or unsafe internals should include: + +- updated invariants or trust assumptions +- negative-path tests +- compatibility notes when public behavior changes +- documentation changes when guarantees or maturity shift diff --git a/docs/ACCOUNT_KERNEL.md b/docs/ACCOUNT_KERNEL.md new file mode 100644 index 0000000..2c38108 --- /dev/null +++ b/docs/ACCOUNT_KERNEL.md @@ -0,0 +1,119 @@ +# Account Kernel + +## Purpose + +The goal is to make authorization explicit, modular, and replay-safe before any `1.0` API freeze. + +## Core Model + +The account subsystem is now organized around: + +- `AccountKernel` + - the orchestrator that runs signer verification, policy checks, and replay protection +- signer interfaces + - `AccountSigner` + - base implementations: direct owner auth, session auth, secp256r1 passkey auth +- policy interfaces + - generic `Policy` + - base implementations for intent expiry, session enforcement, active device checks, and guardian checks +- signed intent schema + - `SignedIntent`, `SignerRef`, `IntentProof` +- structured auth results + - `AuthResult`, `AuthMethod` + +## Signed Intent Schema + +`SignedIntent` binds: + +- target account +- signer reference +- action payload +- nonce +- expiry +- deterministic `action_hash` +- proof material + +The deterministic hash is derived from: + +- nonce +- expiry +- signer identity fields +- action system name +- action bytes + +## Replay Protection + +Cougr uses two replay domains: + +- per-account nonce tracking for direct owner auth and passkey auth +- per-session nonce tracking for session intents + +The replay implementation lives in: + +- [src/accounts/replay.rs](../src/accounts/replay.rs) +- [src/accounts/storage.rs](../src/accounts/storage.rs) + +## Session Model + +Session state now includes: + +- unique `key_id` +- scoped allowed actions +- operation budget +- expiration timestamp +- `next_nonce` + +Session enforcement requires all of: + +- session exists +- action is in scope +- session is not expired +- operation budget remains +- intent nonce matches `next_nonce` + +On success the session consumes one operation and advances `next_nonce`. + +## Signers + +Current base signer implementations: + +- direct owner signer + - uses `require_auth` +- session signer + - explicit non-fallback session path evaluated by the kernel +- secp256r1 passkey signer + - verifies signatures against registered passkeys + +## Policies + +The policy layer is intentionally reusable across account features. + +Current base policies: + +- `IntentExpiryPolicy` +- `SessionPolicy` +- `ActiveDevicePolicy` +- `GuardianPolicy` + +This is how device and recovery support now live under the same policy model instead of ad hoc checks. + +## Auth Results + +`AuthResult` returns structured information instead of only `Result<(), AccountError>`. + +Current fields: + +- method used +- nonce consumed +- session key id, when applicable +- remaining operations, when applicable + +## GameWorld Integration + +`GameWorld` now exposes: + +- direct owner auth via `execute_authorized` +- explicit kernel execution via `execute_intent` +- explicit session execution via `execute_with_active_session` + +This removes the old dependency on implicit session-to-owner fallback from the primary auth path. diff --git a/docs/API_CONTRACT.md b/docs/API_CONTRACT.md new file mode 100644 index 0000000..3760ab0 --- /dev/null +++ b/docs/API_CONTRACT.md @@ -0,0 +1,148 @@ +# Cougr Public API Contract + +## Purpose + +This document defines how Cougr presents its public Rust API before `1.0`. + +It answers four practical questions: + +- which entrypoints are central to the product +- which surfaces are usable but still evolving +- which modules should not be interpreted as production commitments +- which compatibility shims or testing helpers are intentionally outside the long-term contract + +## API Positioning + +Cougr currently exposes a broad surface for a pre-`1.0` crate. That is acceptable only if the repository is explicit about which parts represent the intended public contract and which parts are still in motion. + +The current product story is: + +- `cougr-core` is primarily an ECS framework for Soroban-compatible applications +- accounts remain Beta, while privacy is split between a stable primitive subset and experimental proof systems +- helper APIs that exist only for compatibility or transition should remain clearly demoted + +## Recommended Public Contract + +### Core entrypoints + +These are the strongest candidates for the long-term public contract: + +- `World` +- `SimpleWorld` +- `ArchetypeWorld` +- typed and raw component operations +- command queues +- scheduling primitives +- events, hooks, and observers +- incremental persistence utilities + +### Supported but evolving surfaces + +These surfaces are useful and implemented, but should continue to be presented as Beta: + +- `accounts` +- `game_world` +- higher-level query helpers +- higher-level scheduler helpers +- proof-submission helpers in `zk` + +### Stable privacy subset + +These privacy surfaces are intentionally narrower and can be presented as Stable: + +- commitments +- commit-reveal +- hidden-state codec interfaces +- Merkle inclusion and sparse Merkle utilities +- `zk::stable` + +### Non-contract surfaces + +These surfaces are public today, but they must not be interpreted as stable commitments: + +- testing-only helpers such as `zk::testing` +- advanced proof-verification APIs whose assumptions are still being hardened +- `zk::experimental` +- compatibility shims retained for transition +- internals-heavy modules whose invariants are not yet documented as stable guarantees + +## Top-Level Surface in `src/lib.rs` + +### Public modules + +Current top-level modules: + +- `accounts` +- `archetype_world` +- `change_tracker` +- `commands` +- `component` +- `debug` behind feature flag +- `error` +- `event` +- `game_world` +- `hooks` +- `incremental` +- `observers` +- `plugin` +- `query` +- `resource` +- `scheduler` +- `simple_world` +- `system` +- `world` +- `zk` + +Internal implementation modules such as legacy demo components, duplicate +system helpers, storage internals, and entity internals are no longer part of +the intended default public surface. They may still exist in the repository, +but the root crate is not meant to advertise them as onboarding entrypoints. + +### Public re-exports + +Current top-level re-exports emphasize: + +- worlds: `World`, `SimpleWorld`, `ArchetypeWorld` +- ECS data: `Component`, `ComponentId`, `ComponentStorage`, `ComponentTrait`, `Position`, `Entity`, `EntityId`, `Resource` +- orchestration: `CommandQueue`, `HookRegistry`, `ObserverRegistry`, `PluginApp`, schedulers +- queries and systems: `Query`, `QueryState`, `System`, `SystemParam` +- accounts and privacy access through explicit namespaces: `accounts`, `zk::stable`, `zk::experimental` + +### Public top-level helper functions + +There are no root-level placeholder helper functions in the supported contract. + +The sanctioned onboarding path is the curated root surface itself: + +- `SimpleWorld` +- `World` +- `ArchetypeWorld` +- `CommandQueue` +- `accounts` +- `zk::{stable, experimental}` + +## Compatibility Exceptions + +### `zk::testing` + +`zk::testing` is a support surface for tests and explicit test utility consumers. +It is not part of the default product contract and is gated to tests or the +`testutils` feature. + +## Public API Risks + +The main public API risks before this cleanup were: + +- the crate exports more surface area than it can reasonably defend as stable +- some internals-heavy modules are public before their long-term contract is clearly documented +- some privacy and verification surfaces are easy to overread as production guarantees +- accounts and privacy modules still include beta-grade behavior that is intentionally documented outside the stable story + +## Direction + +The current cleanup direction is: + +1. define a smaller golden path for `cougr-core` +2. separate stable and experimental privacy surfaces more aggressively +3. reduce exposure of internals-heavy modules where no durable contract exists +4. keep documentation aligned with code reality and compatibility intent diff --git a/docs/MATURITY_MODEL.md b/docs/MATURITY_MODEL.md new file mode 100644 index 0000000..d48ca3e --- /dev/null +++ b/docs/MATURITY_MODEL.md @@ -0,0 +1,92 @@ +# Cougr Maturity Model + +## Purpose + +This document defines how Cougr classifies public surfaces before `1.0`. + +The goal is straightforward: stability, documentation, and compatibility promises must match the actual implementation. + +## Levels + +### Stable + +Stable features are: + +- SemVer-protected +- documented with invariants and intended usage +- covered by focused tests, including negative paths where relevant +- safe to present as part of Cougr's long-term public contract + +Stable features must not contain: + +- placeholder identifiers +- incomplete public behavior +- undocumented fallback logic in security-critical paths +- undocumented storage or auth assumptions + +### Beta + +Beta features are: + +- usable and actively supported +- expected to evolve before `1.0` +- covered by tests, but not yet frozen in API or guarantees + +Beta is the right classification for modules that have real implementation value but still need one or more of: + +- tighter interface design +- stronger invariants +- stronger security posture +- compatibility cleanup + +### Experimental + +Experimental features are: + +- exploratory or fast-moving +- not part of Cougr's stable promise +- allowed to change or be removed without compatibility guarantees + +Experimental is the default for features where: + +- the implementation contract is still incomplete +- external security assumptions are still being clarified +- ecosystem support is still emerging +- the API shape is not yet trustworthy enough to freeze + +## Current Baseline + +| Surface | Status | Notes | +|---|---|---| +| ECS runtime, worlds, storage, scheduling | Beta | Broadly usable, with a curated root facade and a smaller onboarding path | +| Accounts and smart-account flows | Beta | Valuable direction, but auth kernel and session enforcement still need redesign | +| Standards layer (`standards`) | Beta | Reusable and integration-tested, but still pre-`1.0` and not yet frozen | +| Commitments, commit-reveal, hidden-state encoding, and Merkle utilities (`zk::stable`) | Stable | Explicitly separated from experimental proof-verification helpers | +| Advanced ZK verification and confidential abstractions | Experimental | Do not treat as stable production primitives yet | +| Testing helpers (`zk::testing`, `MockAccount`) | Non-stable support surface | Intended only for tests or explicit test utility consumers | + +## Rules for Public Surfaces + +- Public APIs that are placeholders must be downgraded or removed from the stable story. +- Testing-only modules should not remain in the default stable surface. +- Documentation must name maturity honestly. +- New security-sensitive features should default to Beta or Experimental unless proven otherwise. + +## Promotion Criteria + +A feature should move toward Stable only when: + +1. its API is intentionally designed and unlikely to churn +2. its invariants are written down +3. its failure modes are documented +4. it has negative-path tests where relevant +5. its compatibility impact is understood + +## Demotion Criteria + +A public surface should be demoted from the stable story when: + +- it contains placeholder logic +- docs overstate what it guarantees +- replay, authorization, or verification semantics are incomplete +- maintainers are not prepared to keep compatibility promises for it diff --git a/docs/PRIVACY_MODEL.md b/docs/PRIVACY_MODEL.md new file mode 100644 index 0000000..5f959d8 --- /dev/null +++ b/docs/PRIVACY_MODEL.md @@ -0,0 +1,115 @@ +# Cougr Privacy Model + +## Purpose + +This document defines Cougr's privacy and proof-verification contract after phase 2. + +Its job is to separate the stable privacy subset from experimental proof systems so +that the repository can make a smaller, stronger claim about what is safe to depend +on before `1.0`. + +## Stable Privacy Surface + +The stable privacy subset in Cougr is: + +- commitments +- commit-reveal flows +- hidden-state encoding interfaces +- Merkle inclusion verification +- sparse Merkle utilities +- privacy interfaces: + - `CommitmentScheme` + - `MerkleProofVerifier` + - `HiddenStateCodec` + - `ProofVerifier` as an interface contract only + +These are exposed through: + +- `cougr_core::zk::stable` +- `cougr_core::zk::stable` + +## Experimental Privacy Surface + +The following remain Experimental: + +- Groth16 proof verification flows +- proof-submission execution helpers +- prebuilt verification circuits +- fog-of-war Merkle exploration orchestration +- multiplayer ZK state-channel transition contracts +- recursive proof-composition descriptors +- advanced hidden-state automation +- hazmat Poseidon-based privacy helpers +- broader confidential-state abstractions + +These are exposed through: + +- `cougr_core::zk::experimental` +- `cougr_core::zk::experimental` + +Compatibility note: + +Experimental modules may still be re-exported from `cougr_core::zk` for transition +convenience, but they are not part of Cougr's stable privacy promise. + +## Privacy Maturity Table + +| Surface | Status | Notes | +|---|---|---| +| Commitments | Stable | Explicit interface and verification contract | +| Commit-reveal | Stable | Explicit component semantics and deadline behavior | +| Hidden-state encoding | Stable | Stable codec interface; fixed-width codecs can be defended | +| Merkle inclusion and sparse Merkle utilities | Stable | Malformed proof behavior and inclusion semantics are explicit | +| Proof submission systems | Beta | Useful orchestration, but still coupled to experimental verification flows | +| Groth16 verification and prebuilt circuits | Experimental | Assumptions are explicit, but not yet strong enough for a stable promise | + +## Proof Verification Contract + +Cougr's experimental Groth16 verifier makes these explicit guarantees: + +- verification keys must satisfy `vk.ic.len() == public_inputs.len() + 1` +- malformed verification-key shape returns `ZKError::InvalidVerificationKey` +- malformed pairing inputs return `ZKError::InvalidInput` +- a well-formed but invalid proof returns `Ok(false)` only when the pairing check fails + +Cougr does not currently claim stronger guarantees for Groth16 around: + +- subgroup validation beyond Soroban host-type decoding +- normalization guarantees beyond fixed-width typed wrappers +- broader proof-system maturity for production confidentiality claims + +That is why the implementation remains Experimental even though the verifier +interface is explicit. + +## Merkle Verification Contract + +Cougr's stable Merkle verification guarantees: + +- malformed proofs with `siblings.len() != depth` return `ZKError::InvalidProofLength` +- well-formed but non-matching proofs return `Ok(false)` +- sparse Merkle utilities produce the same on-chain proof representation used by + the stable SHA256 verifier + +## Hidden-State Encoding Contract + +Stable hidden-state codecs must: + +- define an exact byte-level representation +- reject malformed encoded state with `ZKError::InvalidInput` +- avoid silent truncation or padding + +The built-in `Bytes32HiddenStateCodec` satisfies this by requiring an exact +32-byte payload in both directions. + +## Relationship to Public Surface + +This model works with: + +- [docs/MATURITY_MODEL.md](docs/MATURITY_MODEL.md) +- [docs/API_CONTRACT.md](docs/API_CONTRACT.md) +- [docs/PUBLIC_GAPS.md](docs/PUBLIC_GAPS.md) +- [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md) + +Any future claim that advanced proof verification is Stable should add stronger +input-validation guarantees, clearer host-assumption boundaries, and tighter +negative-path coverage than exists today. diff --git a/docs/PUBLIC_GAPS.md b/docs/PUBLIC_GAPS.md new file mode 100644 index 0000000..2f06f3f --- /dev/null +++ b/docs/PUBLIC_GAPS.md @@ -0,0 +1,61 @@ +# Public Surface Gaps + +## Purpose + +This document lists public-facing behaviors and constraints that remain intentionally outside Cougr's stable promise after the phase 0 foundation reset. + +The goal is to keep the stable story honest. If a behavior is still evolving, security-sensitive, or not yet defensible as a long-term contract, it belongs here instead of being implied as stable. + +## Current Gaps + +### Accounts and Smart-Account Flows + +Status: Beta + +Remaining gaps: + +- session semantics are implemented, but the long-term auth kernel is still scheduled for redesign in phase 1 +- replay protection is not yet presented as a frozen cross-module contract +- fallback authorization behavior is documented, but it should still be reviewed carefully per integration + +### Privacy and ZK + +Status: Stable subset plus Experimental extensions + +Remaining gaps: + +- stable privacy primitives are intentionally narrow and do not imply stable advanced proof verification +- advanced proof-verification and confidential abstractions are explicitly Experimental +- proof-submission orchestration remains Beta where it depends on experimental verification flows + +### Broad Top-Level Crate Surface + +Status: Beta + +Remaining gaps: + +- the crate still exports more modules than the eventual stable golden path is likely to keep +- internals-heavy modules remain public in places where durable invariants are not yet fully documented + +### Standards Layer + +Status: Beta + +Remaining gaps: + +- reusable standards are now implemented, but their long-term SemVer-frozen surface is not promised before `1.0` +- integrating contracts are still responsible for composing caller authentication around generic state-machine helpers such as `Pausable` and `RecoveryGuard` + +## Removed or Downgraded During Phase 0 + +- deprecated placeholder helpers were removed from `src/lib.rs` in favor of the curated root API +- `zk::testing` remains outside the default product contract and is only available for tests or `testutils` +- accounts and advanced privacy features are described as Beta or Experimental rather than stable production guarantees + +## How To Use This List + +Treat this document as the current boundary between: + +- what Cougr can present as intentionally supported today +- what is still usable but evolving +- what must not be interpreted as part of a stable production contract diff --git a/docs/STANDARDS_LAYER.md b/docs/STANDARDS_LAYER.md new file mode 100644 index 0000000..30e6c1c --- /dev/null +++ b/docs/STANDARDS_LAYER.md @@ -0,0 +1,100 @@ +# Standards Layer + +## Purpose + +The standards layer introduces reusable, storage-aware contract primitives in the style of OpenZeppelin building blocks, but shaped for Cougr's Soroban-oriented single-crate model. + +These modules are meant to be composed into application contracts and account flows without depending on any example project. + +## Included Standards + +### `Ownable` + +- single-owner access primitive +- explicit initialization +- direct transfer and renounce flows +- typed ownership transition events + +### `Ownable2Step` + +- staged ownership handoff +- pending-owner tracking in storage +- explicit acceptance requirement before ownership changes +- cancellation support for abandoned handoffs + +### `AccessControl` + +- role-based authorization keyed by `Symbol` +- per-role admin delegation +- explicit grant, revoke, and renounce semantics +- default admin role for bootstrapping new modules + +### `Pausable` + +- storage-backed emergency stop flag +- explicit paused and unpaused transitions +- guard methods for mutating entrypoints + +### `ExecutionGuard` + +- storage-backed execution lock +- suited for reentrancy-like protection and mutation serialization +- can be used as explicit enter/exit calls or as a scoped closure wrapper + +### `RecoveryGuard` + +- blocks sensitive flows while a recovery window is active +- generic enough to compose with account recovery or application-defined incident response + +### `BatchExecutor` + +- reusable batch length validation +- single-path execution semantics for collections of operations +- explicit empty and oversize rejection + +### `DelayedExecutionPolicy` + +- storage-backed delayed operation queue +- deterministic operation IDs +- readiness and expiry checks +- cancellation and execution events + +## Storage and Namespacing + +Each standards module is instantiated with a `Symbol` identifier. + +That identifier becomes part of the storage key, which allows a single contract to host multiple independent instances of the same standard without collisions. + +## Authorization Model + +These modules do not assume hidden caller semantics. + +Where authorization matters: + +- `Ownable` and `Ownable2Step` require an explicit caller address +- `AccessControl` checks the caller against the relevant admin role +- `Pausable`, `RecoveryGuard`, and similar state machines leave the surrounding authorization decision to the integrating contract + +This is intentional. Cougr keeps authorization visible at the integration boundary instead of burying it in generic helpers. + +## Error Semantics + +The standards layer uses `StandardsError` for consistent negative-path behavior across integrations. + +Important failure modes include: + +- unauthorized caller +- duplicate initialization +- missing or mismatched pending owner +- duplicate role grant or missing role during revoke +- paused versus not-paused guard failures +- execution lock contention +- recovery-active guard failure +- empty or oversized batches +- delayed operation not ready, expired, already executed, or missing + +## Maturity + +Status: Beta + +The standards are reusable and integration-tested, but still pre-`1.0`. Their API is intentionally designed and documented, yet still subject to refinement while Cougr finalizes its broader stable surface. diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md new file mode 100644 index 0000000..f38e2cb --- /dev/null +++ b/docs/THREAT_MODEL.md @@ -0,0 +1,130 @@ +# Cougr Threat Model + +## Purpose + +This document defines the current threat model baseline for Cougr before `1.0`. + +It is not a claim of formal verification or exhaustive review. It exists so the repository is explicit about what must be defended, where trust assumptions live, and which subsystems need extra scrutiny before production use. + +## Security Objectives + +Cougr aims to preserve: + +- authorization correctness for gameplay and account actions +- replay resistance for session-like or delegated actions +- storage integrity for ECS state and account-linked records +- proof-verification correctness for accepted privacy and ZK flows +- predictable state transitions when authorization depends on mutation ordering + +## Sensitive Subsystems + +The most sensitive subsystems in the current repo are: + +- `accounts` + - authorization decisions + - session creation and revocation + - recovery, device, and fallback flows +- `world`, `simple_world`, `archetype_world`, `commands`, `scheduler` + - mutation ordering and delayed execution + - correctness of state reads used by auth or proof logic +- `incremental` and persistent storage helpers + - durability and consistency of serialized state +- `zk` + - proof verification assumptions + - commitment and Merkle verification + - malformed-input handling + +## Threat Actors + +Assume the following threat classes: + +- untrusted users submitting arbitrary contract inputs +- authorized users attempting to exceed granted session scope +- integrators treating beta or experimental modules as stronger guarantees than documented +- adversaries replaying previously valid auth or proof material +- malformed or adversarial proof inputs targeting verification edges + +## Out-of-Scope Guarantees + +Cougr does not currently guarantee: + +- audit-backed production readiness across all public modules +- complete replay resistance across every future integration pattern +- hardened contracts for every advanced ZK abstraction +- compatibility guarantees for non-stable support surfaces + +## Primary Threat Areas + +### Authorization and Session Flows + +Primary risks: + +- scope bypass +- expired or exhausted session reuse +- ambiguous fallback behavior +- weak or colliding session identifiers + +Current posture: + +- accounts remain Beta +- session semantics are usable, but the long-term auth kernel is still evolving + +### Replay and Intent Reuse + +Primary risks: + +- repeating previously valid actions +- reusing delegated auth artifacts outside intended scope +- lack of globally coherent nonce or intent semantics + +Current posture: + +- replay-sensitive behavior must be reviewed integration by integration +- roadmap phase 1 continues the deeper replay-protection work + +### ECS State Integrity + +Primary risks: + +- authorization depending on stale state +- unsafe assumptions around deferred commands +- observer or scheduler ordering changing effective behavior + +Current posture: + +- ECS primitives are broadly usable +- stable invariants are not yet frozen across the whole public surface + +### Proof Verification and Privacy + +Primary risks: + +- accepting malformed proofs or malformed public inputs +- overstating the maturity of advanced verification flows +- confusing stable commitments and Merkle utilities with broader confidential abstractions + +Current posture: + +- commitments, commit-reveal, hidden-state codecs, and Merkle utilities are the stable privacy subset +- advanced ZK verification remains Experimental + +## Required Review Areas Before Production Use + +Review at minimum: + +- auth entrypoints and fallback logic +- session scope, expiration, and revocation behavior +- replay assumptions in the integrating application +- persistent storage schema assumptions +- proof-verification failure modes +- state-transition ordering where auth depends on world mutations + +## Relationship to Maturity + +This threat model works with: + +- [docs/MATURITY_MODEL.md](docs/MATURITY_MODEL.md) +- [docs/API_CONTRACT.md](docs/API_CONTRACT.md) +- [SECURITY.md](../SECURITY.md) + +Any future claim that a subsystem is Stable should be accompanied by tighter invariants, explicit failure modes, and threat-model updates where applicable. diff --git a/doocs/PRIVACY_MODEL.md b/doocs/PRIVACY_MODEL.md new file mode 100644 index 0000000..2d05094 --- /dev/null +++ b/doocs/PRIVACY_MODEL.md @@ -0,0 +1,5 @@ +# Cougr Privacy Model + +Canonical source: [docs/PRIVACY_MODEL.md](../docs/PRIVACY_MODEL.md) + +This mirror exists so the phase 2 roadmap artifacts remain discoverable from `doocs/`. diff --git a/examples/chess/README.md b/examples/chess/README.md index 5cab614..c4204a1 100644 --- a/examples/chess/README.md +++ b/examples/chess/README.md @@ -121,7 +121,7 @@ All components implement `cougr_core::component::ComponentTrait` for type-safe s ### Move Validation Circuit -Uses `cougr_core::zk::circuits::CustomCircuitBuilder`: +Uses `cougr_core::zk::experimental::CustomCircuitBuilder`: ```rust let circuit = CustomCircuit::builder(vk) diff --git a/examples/chess/src/lib.rs b/examples/chess/src/lib.rs index ce60774..1fbc763 100644 --- a/examples/chess/src/lib.rs +++ b/examples/chess/src/lib.rs @@ -1,8 +1,8 @@ #![no_std] use cougr_core::component::ComponentTrait; -use cougr_core::zk::circuits::CustomCircuit; -use cougr_core::zk::types::{Groth16Proof, VerificationKey}; +use cougr_core::zk::experimental::CustomCircuit; +use cougr_core::zk::{Groth16Proof, VerificationKey}; use soroban_sdk::{ contract, contractimpl, contracttype, symbol_short, Address, Bytes, BytesN, Env, Map, Symbol, }; diff --git a/examples/chess/src/test.rs b/examples/chess/src/test.rs index 520c76d..27c38cb 100644 --- a/examples/chess/src/test.rs +++ b/examples/chess/src/test.rs @@ -1,5 +1,5 @@ use super::*; -use cougr_core::zk::types::{G1Point, G2Point}; +use cougr_core::zk::{G1Point, G2Point}; use soroban_sdk::{testutils::Address as _, Address, BytesN, Env, Vec}; fn setup_game() -> (Env, ChessContractClient<'static>, Address, Address) { diff --git a/examples/guild_arena/src/lib.rs b/examples/guild_arena/src/lib.rs index 2abe2f5..be2c6fe 100644 --- a/examples/guild_arena/src/lib.rs +++ b/examples/guild_arena/src/lib.rs @@ -4,8 +4,10 @@ use soroban_sdk::{ contract, contractimpl, contracttype, symbol_short, Address, BytesN, Env, Symbol, Vec, }; -use cougr_core::accounts::multi_device::{DeviceManager, DevicePolicy, MultiDeviceProvider}; -use cougr_core::accounts::recovery::{RecoverableAccount, RecoveryConfig, RecoveryProvider}; +use cougr_core::accounts::{ + DeviceManager, DevicePolicy, MultiDeviceProvider, RecoverableAccount, RecoveryConfig, + RecoveryProvider, +}; use cougr_core::component::ComponentTrait; // --- Components --- diff --git a/examples/rock_paper_scissors/README.md b/examples/rock_paper_scissors/README.md index b02e55e..e96c2b1 100644 --- a/examples/rock_paper_scissors/README.md +++ b/examples/rock_paper_scissors/README.md @@ -216,7 +216,7 @@ This example uses SHA256 for simplicity and immediate usability. Poseidon2 is a **For production ZK applications**, use Poseidon2: ```rust -use cougr_core::zk::crypto::poseidon2_hash; +use cougr_core::zk::experimental::poseidon2_hash; let hash = poseidon2_hash(&env, ¶ms, &choice_u256, &salt_u256); ``` diff --git a/examples/space_invaders/Cargo.lock b/examples/space_invaders/Cargo.lock index 86da8b1..40668bd 100644 --- a/examples/space_invaders/Cargo.lock +++ b/examples/space_invaders/Cargo.lock @@ -277,7 +277,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cougr-core" -version = "0.0.1" +version = "0.5.0" dependencies = [ "soroban-sdk", "wee_alloc", diff --git a/examples/space_invaders/README.md b/examples/space_invaders/README.md index 3cb7f4b..95db175 100644 --- a/examples/space_invaders/README.md +++ b/examples/space_invaders/README.md @@ -50,7 +50,7 @@ This example demonstrates how to build on-chain game logic on the Stellar blockc ```rust // Using cougr-core's Position component -use cougr_core::components::Position as CougrPosition; +use cougr_core::Position as CougrPosition; // Entity with Position, Velocity, and Health components pub struct Bullet { diff --git a/examples/space_invaders/src/game_state.rs b/examples/space_invaders/src/game_state.rs index 9c1ca8a..65442c4 100644 --- a/examples/space_invaders/src/game_state.rs +++ b/examples/space_invaders/src/game_state.rs @@ -10,7 +10,7 @@ use soroban_sdk::contracttype; // Import cougr-core Position component for entity position tracking // This demonstrates proper integration of cougr-core into game logic -pub use cougr_core::components::Position as CougrPosition; +pub use cougr_core::Position as CougrPosition; /// Direction for ship movement #[contracttype] diff --git a/examples/space_invaders/src/lib.rs b/examples/space_invaders/src/lib.rs index 552f7c9..2bea388 100644 --- a/examples/space_invaders/src/lib.rs +++ b/examples/space_invaders/src/lib.rs @@ -40,7 +40,7 @@ use soroban_sdk::{contract, contractimpl, Env, Vec}; // Import cougr-core ECS framework components // These are actively used for entity management and position tracking -use cougr_core::components::Position as CougrPosition; +use cougr_core::Position as CougrPosition; // Re-export game state types for external use pub use game_state::{ @@ -64,7 +64,7 @@ impl SpaceInvadersContract { /// persists the game state on-chain. pub fn init_game(env: Env) { // Create cougr-core ECS World for entity management - let mut world = cougr_core::create_world(); + let mut world = cougr_core::World::new(); // Spawn player ship entity in ECS World let _ship_entity = world.spawn_empty(); diff --git a/examples/tap_battle/README.md b/examples/tap_battle/README.md index 9502181..1407f77 100644 --- a/examples/tap_battle/README.md +++ b/examples/tap_battle/README.md @@ -57,7 +57,7 @@ This example showcases two key cougr-core features: ### Passkey Authentication (`secp256r1_auth`) ```rust -use cougr_core::accounts::secp256r1_auth::{Secp256r1Key, Secp256r1Storage, verify_secp256r1}; +use cougr_core::accounts::{Secp256r1Key, Secp256r1Storage, verify_secp256r1}; // Register: store public key on-chain Secp256r1Storage::store(&env, &player, &key); @@ -69,7 +69,7 @@ verify_secp256r1(&env, &pubkey, &message, &signature)?; ### Session Builder (`session_builder`) ```rust -use cougr_core::accounts::session_builder::SessionBuilder; +use cougr_core::accounts::SessionBuilder; // Create scoped session for gasless gameplay let scope = SessionBuilder::new(&env) diff --git a/examples/tap_battle/src/auth.rs b/examples/tap_battle/src/auth.rs index 8ce31e1..beab496 100644 --- a/examples/tap_battle/src/auth.rs +++ b/examples/tap_battle/src/auth.rs @@ -8,8 +8,7 @@ use soroban_sdk::{symbol_short, Address, Bytes, BytesN, Env, Symbol}; -use cougr_core::accounts::secp256r1_auth::{verify_secp256r1, Secp256r1Key, Secp256r1Storage}; -use cougr_core::accounts::session_builder::SessionBuilder; +use cougr_core::accounts::{verify_secp256r1, Secp256r1Key, Secp256r1Storage, SessionBuilder}; use crate::types::*; diff --git a/examples/tap_battle/src/game.rs b/examples/tap_battle/src/game.rs index 2558e6f..ce4d909 100644 --- a/examples/tap_battle/src/game.rs +++ b/examples/tap_battle/src/game.rs @@ -28,7 +28,7 @@ pub fn process_tap(env: &Env, player: &Address) -> TapResult { .storage() .persistent() .get(&DataKey::TapState(player.clone())) - .unwrap_or(TapCounter::new()); + .unwrap_or_default(); let current_ledger = env.ledger().sequence() as u64; @@ -60,12 +60,12 @@ pub fn process_tap(env: &Env, player: &Address) -> TapResult { // === POWER-UP CHARGING === // Every COMBO_CHARGE_THRESHOLD combo taps, earn a power-up charge - if tap_state.combo > 0 && tap_state.combo % COMBO_CHARGE_THRESHOLD == 0 { + if tap_state.combo > 0 && tap_state.combo.is_multiple_of(COMBO_CHARGE_THRESHOLD) { let mut power_up: PowerUp = env .storage() .persistent() .get(&DataKey::PowerUpState(player.clone())) - .unwrap_or(PowerUp::new(PowerUpKind::DoubleTap as u32)); + .unwrap_or_else(|| PowerUp::new(PowerUpKind::DoubleTap as u32)); power_up.charges += 1; env.storage() .persistent() @@ -241,7 +241,7 @@ pub fn get_profile(env: &Env, player: &Address) -> PlayerProfile { env.storage() .persistent() .get(&DataKey::Profile(player.clone())) - .unwrap_or(PlayerProfile::new()) + .unwrap_or_default() } // ============================================================================ diff --git a/examples/tap_battle/src/lib.rs b/examples/tap_battle/src/lib.rs index bfd5454..5a6302d 100644 --- a/examples/tap_battle/src/lib.rs +++ b/examples/tap_battle/src/lib.rs @@ -31,7 +31,7 @@ mod test; use soroban_sdk::{contract, contractimpl, Address, Bytes, BytesN, Env}; -// cougr_core ECS integration used via cougr_core::create_world() +// cougr_core ECS integration uses the canonical `World::new()` entrypoint. // Re-export types for external use pub use types::*; @@ -52,7 +52,7 @@ impl TapBattleContract { /// * `pubkey` - SEC-1 uncompressed secp256r1 public key (65 bytes) pub fn register_passkey(env: Env, player: Address, pubkey: BytesN<65>) { // Create cougr-core ECS World for entity management - let mut world = cougr_core::create_world(); + let mut world = cougr_core::World::new(); let _player_entity = world.spawn_empty(); auth::register_passkey(&env, &player, &pubkey); diff --git a/examples/trading_card_game/src/lib.rs b/examples/trading_card_game/src/lib.rs index f78ef94..b411587 100644 --- a/examples/trading_card_game/src/lib.rs +++ b/examples/trading_card_game/src/lib.rs @@ -1,8 +1,6 @@ #![no_std] -use cougr_core::accounts::batch::BatchBuilder; -use cougr_core::accounts::session_builder::SessionBuilder; -use cougr_core::accounts::types::GameAction; +use cougr_core::accounts::{BatchBuilder, GameAction, SessionBuilder}; use cougr_core::component::ComponentTrait; use soroban_sdk::{ contract, contracterror, contractimpl, contracttype, panic_with_error, symbol_short, Address, diff --git a/src/accounts/batch.rs b/src/accounts/batch.rs index 9310bbb..dc7bb05 100644 --- a/src/accounts/batch.rs +++ b/src/accounts/batch.rs @@ -11,13 +11,18 @@ use super::types::GameAction; /// and then returned for execution by the caller. /// /// # Example -/// ```ignore +/// ```no_run +/// # use cougr_core::accounts::{BatchBuilder, CougrAccount, GameAction}; +/// # use soroban_sdk::{symbol_short, Bytes, Env}; +/// # fn run(env: &Env, account: &A) -> Result<(), cougr_core::accounts::AccountError> { /// let mut batch = BatchBuilder::new(); -/// batch.add(GameAction { system_name: symbol_short!("move"), data: ... }); -/// batch.add(GameAction { system_name: symbol_short!("attack"), data: ... }); +/// batch.add(GameAction { system_name: symbol_short!("move"), data: Bytes::new(env) }); +/// batch.add(GameAction { system_name: symbol_short!("attack"), data: Bytes::new(env) }); /// -/// let executed = batch.execute(&env, &account)?; -/// // Now apply each action to the world +/// let executed = batch.execute(env, account)?; +/// assert_eq!(executed.len(), 2); +/// # Ok(()) +/// # } /// ``` pub struct BatchBuilder { actions: Vec, diff --git a/src/accounts/classic.rs b/src/accounts/classic.rs index a609405..53efb06 100644 --- a/src/accounts/classic.rs +++ b/src/accounts/classic.rs @@ -1,7 +1,9 @@ use soroban_sdk::{Address, Env}; use super::error::AccountError; -use super::traits::CougrAccount; +use super::intent::{AuthResult, SignedIntent}; +use super::kernel::AccountKernel; +use super::traits::{CougrAccount, IntentAccount}; use super::types::{AccountCapabilities, GameAction}; /// A Classic Stellar account (G-address). @@ -39,6 +41,16 @@ impl CougrAccount for ClassicAccount { } } +impl IntentAccount for ClassicAccount { + fn authorize_intent( + &mut self, + env: &Env, + intent: &SignedIntent, + ) -> Result { + AccountKernel::new(self.address.clone()).authorize(env, intent) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/accounts/contract.rs b/src/accounts/contract.rs index 7b18f34..20511d9 100644 --- a/src/accounts/contract.rs +++ b/src/accounts/contract.rs @@ -1,8 +1,10 @@ use soroban_sdk::{Address, BytesN, Env}; use super::error::AccountError; +use super::intent::{AuthResult, SignedIntent}; +use super::kernel::AccountKernel; use super::storage::SessionStorage; -use super::traits::{CougrAccount, SessionKeyProvider}; +use super::traits::{CougrAccount, IntentAccount, SessionKeyProvider}; use super::types::{AccountCapabilities, GameAction, SessionKey, SessionScope}; /// A Contract Stellar account (C-address). @@ -40,40 +42,37 @@ impl CougrAccount for ContractAccount { } } - fn authorize(&self, env: &Env, action: &GameAction) -> Result<(), AccountError> { - // Check if any active session key covers this action - let keys = SessionStorage::load_all(env, &self.address); - for i in 0..keys.len() { - if let Some(key) = keys.get(i) { - let mut found = false; - for j in 0..key.scope.allowed_actions.len() { - if key.scope.allowed_actions.get(j).unwrap() == action.system_name { - found = true; - break; - } - } - if found && key.operations_used < key.scope.max_operations { - return Ok(()); - } - } - } - // Fallback to require_auth + fn authorize(&self, _env: &Env, action: &GameAction) -> Result<(), AccountError> { + let _ = action; self.address.require_auth(); Ok(()) } } +impl IntentAccount for ContractAccount { + fn authorize_intent( + &mut self, + env: &Env, + intent: &SignedIntent, + ) -> Result { + AccountKernel::new(self.address.clone()).authorize(env, intent) + } +} + impl SessionKeyProvider for ContractAccount { fn create_session( &mut self, env: &Env, scope: SessionScope, ) -> Result { + let existing = SessionStorage::load_all(env, &self.address).len(); + let key_id = session_key_id(env, existing, &scope); let key = SessionKey { - key_id: BytesN::from_array(env, &[0u8; 32]), // placeholder key ID + key_id, scope, created_at: env.ledger().timestamp(), operations_used: 0, + next_nonce: 0, }; SessionStorage::store(env, &self.address, &key); Ok(key) @@ -92,6 +91,10 @@ impl SessionKeyProvider for ContractAccount { return Ok(false); } + if SessionStorage::load(env, &self.address, &key.key_id).is_none() { + return Ok(false); + } + Ok(true) } @@ -103,6 +106,17 @@ impl SessionKeyProvider for ContractAccount { } } +fn session_key_id(env: &Env, existing_sessions: u32, scope: &SessionScope) -> BytesN<32> { + let mut bytes = [0u8; 32]; + bytes[0..8].copy_from_slice(&env.ledger().timestamp().to_be_bytes()); + bytes[8..12].copy_from_slice(&env.ledger().sequence().to_be_bytes()); + bytes[12..16].copy_from_slice(&existing_sessions.to_be_bytes()); + bytes[16..20].copy_from_slice(&(scope.allowed_actions.len()).to_be_bytes()); + bytes[20..24].copy_from_slice(&scope.max_operations.to_be_bytes()); + bytes[24..32].copy_from_slice(&scope.expires_at.to_be_bytes()); + BytesN::from_array(env, &bytes) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/accounts/degradation.rs b/src/accounts/degradation.rs index b87a13b..46ade3a 100644 --- a/src/accounts/degradation.rs +++ b/src/accounts/degradation.rs @@ -113,6 +113,7 @@ mod tests { }, created_at: 0, operations_used: 0, + next_nonce: 0, } } diff --git a/src/accounts/error.rs b/src/accounts/error.rs index dae1c77..68bb67e 100644 --- a/src/accounts/error.rs +++ b/src/accounts/error.rs @@ -22,4 +22,12 @@ pub enum AccountError { DeviceLimitReached = 34, DeviceNotFound = 35, RecoveryAlreadyActive = 36, + NonceMismatch = 37, + ActionNotAllowed = 38, + SessionBudgetExceeded = 39, + IntentExpired = 40, + InvalidIntent = 41, + SignerMismatch = 42, + SessionRevoked = 43, + SignerNotRegistered = 44, } diff --git a/src/accounts/intent.rs b/src/accounts/intent.rs new file mode 100644 index 0000000..eff363b --- /dev/null +++ b/src/accounts/intent.rs @@ -0,0 +1,212 @@ +use soroban_sdk::{contracttype, Address, Bytes, BytesN, Env, Symbol, Val}; + +use super::types::GameAction; + +/// Supported intent signer kinds for the account kernel. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum IntentSigner { + Direct = 0, + Session = 1, + Passkey = 2, +} + +/// Stable identifier for the signer used by an intent. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SignerRef { + pub kind: IntentSigner, + pub session_key_id: BytesN<32>, + pub label: Symbol, +} + +/// Signature container for intent verification. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum IntentProofKind { + None = 0, + Secp256r1 = 1, +} + +/// Signature bytes for a signed intent. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IntentProof { + pub kind: IntentProofKind, + pub signature: BytesN<64>, +} + +/// Canonical signed intent schema for account authorization. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SignedIntent { + pub account: Address, + pub signer: SignerRef, + pub action: GameAction, + pub nonce: u64, + pub expires_at: u64, + pub action_hash: BytesN<32>, + pub proof: IntentProof, +} + +/// Result of a successful authorization. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum AuthMethod { + Direct = 0, + Session = 1, + Passkey = 2, +} + +/// Structured authorization result returned by the kernel. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AuthResult { + pub method: AuthMethod, + pub nonce_consumed: u64, + pub session_key_id: BytesN<32>, + pub remaining_operations: u32, +} + +impl SignerRef { + pub fn direct(env: &Env) -> Self { + Self { + kind: IntentSigner::Direct, + session_key_id: BytesN::from_array(env, &[0u8; 32]), + label: Symbol::new(env, ""), + } + } + + pub fn session(env: &Env, key_id: &BytesN<32>) -> Self { + Self { + kind: IntentSigner::Session, + session_key_id: key_id.clone(), + label: Symbol::new(env, ""), + } + } + + pub fn passkey(env: &Env, label: Symbol) -> Self { + Self { + kind: IntentSigner::Passkey, + session_key_id: BytesN::from_array(env, &[0u8; 32]), + label, + } + } +} + +impl IntentProof { + pub fn none(env: &Env) -> Self { + Self { + kind: IntentProofKind::None, + signature: BytesN::from_array(env, &[0u8; 64]), + } + } + + pub fn secp256r1(signature: BytesN<64>) -> Self { + Self { + kind: IntentProofKind::Secp256r1, + signature, + } + } +} + +impl SignedIntent { + pub fn direct( + env: &Env, + account: Address, + action: GameAction, + nonce: u64, + expires_at: u64, + ) -> Self { + let signer = SignerRef::direct(env); + let action_hash = hash_intent(env, &signer, &action, nonce, expires_at); + Self { + account, + signer, + action, + nonce, + expires_at, + action_hash, + proof: IntentProof::none(env), + } + } + + pub fn session( + env: &Env, + account: Address, + key_id: &BytesN<32>, + action: GameAction, + nonce: u64, + expires_at: u64, + ) -> Self { + let signer = SignerRef::session(env, key_id); + let action_hash = hash_intent(env, &signer, &action, nonce, expires_at); + Self { + account, + signer, + action, + nonce, + expires_at, + action_hash, + proof: IntentProof::none(env), + } + } + + pub fn passkey( + env: &Env, + account: Address, + label: Symbol, + action: GameAction, + nonce: u64, + expires_at: u64, + signature: BytesN<64>, + ) -> Self { + let signer = SignerRef::passkey(env, label); + let action_hash = hash_intent(env, &signer, &action, nonce, expires_at); + Self { + account, + signer, + action, + nonce, + expires_at, + action_hash, + proof: IntentProof::secp256r1(signature), + } + } + + pub fn recompute_hash(&self, env: &Env) -> BytesN<32> { + hash_intent(env, &self.signer, &self.action, self.nonce, self.expires_at) + } +} + +pub fn hash_intent( + env: &Env, + signer: &SignerRef, + action: &GameAction, + nonce: u64, + expires_at: u64, +) -> BytesN<32> { + let mut bytes = Bytes::new(env); + bytes.append(&Bytes::from_slice(env, &nonce.to_be_bytes())); + bytes.append(&Bytes::from_slice(env, &expires_at.to_be_bytes())); + bytes.append(&Bytes::from_slice( + env, + &(signer.kind.clone() as u32).to_be_bytes(), + )); + bytes.append(&Bytes::from_slice(env, &signer.session_key_id.to_array())); + let label_bits: Val = signer.label.to_val(); + bytes.append(&Bytes::from_slice( + env, + &label_bits.get_payload().to_be_bytes(), + )); + let action_bits: Val = action.system_name.to_val(); + bytes.append(&Bytes::from_slice( + env, + &action_bits.get_payload().to_be_bytes(), + )); + bytes.append(&action.data); + BytesN::from_array(env, &env.crypto().sha256(&bytes).to_array()) +} diff --git a/src/accounts/kernel.rs b/src/accounts/kernel.rs new file mode 100644 index 0000000..3aa993b --- /dev/null +++ b/src/accounts/kernel.rs @@ -0,0 +1,342 @@ +use soroban_sdk::{Address, Env}; + +use super::error::AccountError; +use super::intent::{AuthMethod, AuthResult, IntentSigner, SignedIntent}; +use super::policy::{IntentContext, IntentExpiryPolicy, Policy, SessionContext, SessionPolicy}; +use super::replay::ReplayProtection; +use super::signer::{AccountSigner, DirectAuthSigner, Secp256r1PasskeySigner, SessionAuthSigner}; +use super::storage::SessionStorage; + +/// Account kernel that separates signer verification, policy evaluation and replay protection. +pub struct AccountKernel { + owner: Address, +} + +impl AccountKernel { + pub fn new(owner: Address) -> Self { + Self { owner } + } + + pub fn owner(&self) -> &Address { + &self.owner + } + + pub fn authorize_direct( + &self, + env: &Env, + intent: &SignedIntent, + ) -> Result { + self.ensure_target(intent)?; + self.ensure_hash(env, intent)?; + + let signer = DirectAuthSigner; + signer.verify(env, &self.owner, intent)?; + + let policy = IntentExpiryPolicy; + policy.evaluate( + env, + &IntentContext { + account: &self.owner, + intent, + }, + )?; + + let consumed = + ReplayProtection::verify_and_consume_account_nonce(env, &self.owner, intent.nonce)?; + + Ok(AuthResult { + method: AuthMethod::Direct, + nonce_consumed: consumed, + session_key_id: zero_key(env), + remaining_operations: 0, + }) + } + + pub fn authorize_session( + &self, + env: &Env, + intent: &SignedIntent, + ) -> Result { + self.ensure_target(intent)?; + self.ensure_hash(env, intent)?; + + let signer = SessionAuthSigner; + signer.verify(env, &self.owner, intent)?; + + let policy = SessionPolicy; + policy.evaluate( + env, + &SessionContext { + account: &self.owner, + intent, + }, + )?; + + let updated = SessionStorage::consume_authorized_session( + env, + &self.owner, + &intent.signer.session_key_id, + )?; + + Ok(AuthResult { + method: AuthMethod::Session, + nonce_consumed: updated.next_nonce - 1, + session_key_id: updated.key_id, + remaining_operations: updated.scope.max_operations - updated.operations_used, + }) + } + + pub fn authorize_passkey( + &self, + env: &Env, + intent: &SignedIntent, + ) -> Result { + self.ensure_target(intent)?; + self.ensure_hash(env, intent)?; + + let signer = Secp256r1PasskeySigner; + signer.verify(env, &self.owner, intent)?; + + let policy = IntentExpiryPolicy; + policy.evaluate( + env, + &IntentContext { + account: &self.owner, + intent, + }, + )?; + + let consumed = + ReplayProtection::verify_and_consume_account_nonce(env, &self.owner, intent.nonce)?; + + Ok(AuthResult { + method: AuthMethod::Passkey, + nonce_consumed: consumed, + session_key_id: zero_key(env), + remaining_operations: 0, + }) + } + + pub fn authorize(&self, env: &Env, intent: &SignedIntent) -> Result { + match intent.signer.kind { + IntentSigner::Direct => self.authorize_direct(env, intent), + IntentSigner::Session => self.authorize_session(env, intent), + IntentSigner::Passkey => self.authorize_passkey(env, intent), + } + } + + fn ensure_target(&self, intent: &SignedIntent) -> Result<(), AccountError> { + if intent.account != self.owner { + return Err(AccountError::Unauthorized); + } + Ok(()) + } + + fn ensure_hash(&self, env: &Env, intent: &SignedIntent) -> Result<(), AccountError> { + if intent.recompute_hash(env) != intent.action_hash { + return Err(AccountError::InvalidIntent); + } + Ok(()) + } +} + +fn zero_key(env: &Env) -> soroban_sdk::BytesN<32> { + soroban_sdk::BytesN::from_array(env, &[0u8; 32]) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::accounts::intent::SignedIntent; + use crate::accounts::multi_device::{DeviceManager, DevicePolicy, MultiDeviceProvider}; + use crate::accounts::policy::{ + ActiveDevicePolicy, DeviceContext, GuardianPolicy, RecoveryContext, + }; + use crate::accounts::recovery::{RecoverableAccount, RecoveryConfig, RecoveryProvider}; + use crate::accounts::storage::SessionStorage; + use crate::accounts::types::{GameAction, SessionKey, SessionScope}; + use soroban_sdk::{ + contract, contractimpl, symbol_short, testutils::Address as _, vec, Address, Bytes, BytesN, + Env, + }; + + #[contract] + pub struct TestContract; + + #[contractimpl] + impl TestContract {} + + fn make_action(env: &Env, name: &str) -> GameAction { + GameAction { + system_name: soroban_sdk::Symbol::new(env, name), + data: Bytes::new(env), + } + } + + #[test] + fn test_direct_intent_consumes_account_nonce() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(TestContract, ()); + let owner = Address::generate(&env); + + env.as_contract(&contract_id, || { + let kernel = AccountKernel::new(owner.clone()); + let action = make_action(&env, "move"); + let intent = SignedIntent::direct(&env, owner, action, 0, 99999); + + let result = kernel.authorize(&env, &intent).unwrap(); + assert_eq!(result.method, AuthMethod::Direct); + assert_eq!( + ReplayProtection::next_account_nonce(&env, kernel.owner()), + 1 + ); + }); + } + + #[test] + fn test_session_intent_enforces_scope_budget_and_nonce() { + let env = Env::default(); + let contract_id = env.register(TestContract, ()); + let owner = Address::generate(&env); + let key_id = BytesN::from_array(&env, &[7u8; 32]); + + env.as_contract(&contract_id, || { + let kernel = AccountKernel::new(owner.clone()); + let session = SessionKey { + key_id: key_id.clone(), + scope: SessionScope { + allowed_actions: vec![&env, symbol_short!("move")], + max_operations: 2, + expires_at: 99999, + }, + created_at: 0, + operations_used: 0, + next_nonce: 0, + }; + SessionStorage::store(&env, &owner, &session); + + let move_1 = SignedIntent::session( + &env, + owner.clone(), + &key_id, + make_action(&env, "move"), + 0, + 99999, + ); + let result_1 = kernel.authorize(&env, &move_1).unwrap(); + assert_eq!(result_1.remaining_operations, 1); + + let move_2 = SignedIntent::session( + &env, + owner.clone(), + &key_id, + make_action(&env, "move"), + 1, + 99999, + ); + let result_2 = kernel.authorize(&env, &move_2).unwrap(); + assert_eq!(result_2.remaining_operations, 0); + + let replay = SignedIntent::session( + &env, + owner.clone(), + &key_id, + make_action(&env, "move"), + 1, + 99999, + ); + assert_eq!( + kernel.authorize(&env, &replay), + Err(AccountError::SessionBudgetExceeded) + ); + }); + } + + #[test] + fn test_session_intent_rejects_wrong_nonce_before_budget_consumption() { + let env = Env::default(); + let contract_id = env.register(TestContract, ()); + let owner = Address::generate(&env); + let key_id = BytesN::from_array(&env, &[8u8; 32]); + + env.as_contract(&contract_id, || { + let kernel = AccountKernel::new(owner.clone()); + let session = SessionKey { + key_id: key_id.clone(), + scope: SessionScope { + allowed_actions: vec![&env, symbol_short!("move")], + max_operations: 3, + expires_at: 99999, + }, + created_at: 0, + operations_used: 0, + next_nonce: 2, + }; + SessionStorage::store(&env, &owner, &session); + + let wrong_nonce = + SignedIntent::session(&env, owner, &key_id, make_action(&env, "move"), 1, 99999); + assert_eq!( + kernel.authorize(&env, &wrong_nonce), + Err(AccountError::NonceMismatch) + ); + }); + } + + #[test] + fn test_device_and_guardian_policies_share_same_policy_model() { + let env = Env::default(); + let contract_id = env.register(TestContract, ()); + let owner = Address::generate(&env); + + env.as_contract(&contract_id, || { + let mut devices = DeviceManager::new( + owner.clone(), + DevicePolicy { + max_devices: 2, + auto_revoke_after: 0, + }, + &env, + ); + let device_key = BytesN::from_array(&env, &[5u8; 32]); + devices + .register_device(&env, device_key.clone(), symbol_short!("phone")) + .unwrap(); + + let mut recovery = RecoverableAccount::new( + owner.clone(), + RecoveryConfig { + threshold: 1, + timelock_period: 0, + max_guardians: 2, + }, + &env, + ); + let guardian = Address::generate(&env); + recovery.add_guardian(&env, guardian.clone()).unwrap(); + + let device_policy = ActiveDevicePolicy; + device_policy + .evaluate( + &env, + &DeviceContext { + account: &owner, + key_id: &device_key, + }, + ) + .unwrap(); + + let guardian_policy = GuardianPolicy; + guardian_policy + .evaluate( + &env, + &RecoveryContext { + account: &owner, + guardian: &guardian, + }, + ) + .unwrap(); + }); + } +} diff --git a/src/accounts/mod.rs b/src/accounts/mod.rs index 1366d48..c6c4b18 100644 --- a/src/accounts/mod.rs +++ b/src/accounts/mod.rs @@ -6,53 +6,79 @@ //! //! ## Architecture //! -//! - **`types`**: Core account types (`GameAction`, `SessionScope`, `SessionKey`, etc.) -//! - **`traits`**: `CougrAccount` and `SessionKeyProvider` traits -//! - **`classic`**: Classic Stellar account implementation -//! - **`contract`**: Contract account with session key support -//! - **`error`**: Account-specific error types -//! - **`testing`**: Mock account for unit testing +//! - root re-exports provide the default onboarding path +//! - advanced flows remain grouped by purpose (`recovery`, `multi_device`, `passkey`) +//! - storage submodules support maintainers and integrations that need lower-level access //! //! ## Usage //! -//! ```ignore -//! use cougr_core::accounts::{ClassicAccount, CougrAccount}; +//! ```no_run +//! use cougr_core::accounts::{ClassicAccount, CougrAccount, GameAction}; +//! use soroban_sdk::{symbol_short, testutils::Address as _, Address, Bytes, Env}; //! +//! let env = Env::default(); +//! let player_address = Address::generate(&env); //! let account = ClassicAccount::new(player_address); +//! let action = GameAction { system_name: symbol_short!("move"), data: Bytes::new(&env) }; //! account.authorize(&env, &action)?; +//! # Ok::<(), cougr_core::accounts::AccountError>(()) //! ``` -pub mod batch; -pub mod classic; -pub mod contract; -pub mod degradation; -pub mod device_storage; -pub mod error; +pub(crate) mod batch; +pub(crate) mod classic; +pub(crate) mod contract; +pub(crate) mod degradation; +pub(crate) mod device_storage; +pub(crate) mod error; +pub(crate) mod intent; +pub(crate) mod kernel; pub mod multi_device; +pub(crate) mod policy; pub mod recovery; -pub mod recovery_storage; -pub mod secp256r1_auth; -pub mod session_builder; -pub mod storage; +pub(crate) mod recovery_storage; +pub(crate) mod replay; +pub(crate) mod secp256r1_auth; +pub(crate) mod session_builder; +pub(crate) mod signer; +pub(crate) mod storage; #[cfg(any(test, feature = "testutils"))] -pub mod testing; -pub mod traits; -pub mod types; +pub(crate) mod testing; +pub(crate) mod traits; +pub(crate) mod types; -// Re-export commonly used items +/// Passkey and WebAuthn helpers. +pub mod passkey { + pub use super::secp256r1_auth::{verify_secp256r1, Secp256r1Key, Secp256r1Storage}; +} + +// Curated root re-exports for the Beta accounts API. pub use batch::BatchBuilder; pub use classic::ClassicAccount; pub use contract::ContractAccount; pub use degradation::{authorize_with_fallback, batch_or_sequential, require_capability}; -pub use device_storage::DeviceStorage; pub use error::AccountError; -pub use multi_device::{DeviceKey, DevicePolicy, MultiDeviceProvider}; -pub use recovery::{Guardian, RecoveryConfig, RecoveryProvider, RecoveryRequest}; -pub use recovery_storage::RecoveryStorage; -pub use secp256r1_auth::{Secp256r1Key, Secp256r1Storage}; +pub use kernel::AccountKernel; +pub use multi_device::{DeviceKey, DeviceManager, DevicePolicy, MultiDeviceProvider}; +pub use recovery::{ + Guardian, RecoverableAccount, RecoveryConfig, RecoveryProvider, RecoveryRequest, +}; +pub use replay::ReplayProtection; +pub use secp256r1_auth::{verify_secp256r1, Secp256r1Key, Secp256r1Storage}; pub use session_builder::SessionBuilder; pub use storage::SessionStorage; +pub use traits::{CougrAccount, IntentAccount, SessionKeyProvider}; +pub use types::{AccountCapabilities, GameAction, SessionKey, SessionScope}; + +// Lower-level support exports for advanced integrations. +pub use device_storage::DeviceStorage; +pub use intent::{ + AuthMethod, AuthResult, IntentProof, IntentProofKind, IntentSigner, SignedIntent, SignerRef, +}; +pub use policy::{ + ActiveDevicePolicy, GuardianPolicy, IntentContext, IntentExpiryPolicy, Policy, RecoveryContext, + SessionContext, SessionPolicy, +}; +pub use recovery_storage::RecoveryStorage; +pub use signer::{AccountSigner, DirectAuthSigner, Secp256r1PasskeySigner, SessionAuthSigner}; #[cfg(any(test, feature = "testutils"))] pub use testing::MockAccount; -pub use traits::{CougrAccount, SessionKeyProvider}; -pub use types::{AccountCapabilities, AuthMethod, GameAction, SessionKey, SessionScope}; diff --git a/src/accounts/policy.rs b/src/accounts/policy.rs new file mode 100644 index 0000000..3006981 --- /dev/null +++ b/src/accounts/policy.rs @@ -0,0 +1,103 @@ +use soroban_sdk::{Address, BytesN, Env}; + +use super::device_storage::DeviceStorage; +use super::error::AccountError; +use super::intent::SignedIntent; +use super::recovery_storage::RecoveryStorage; +use super::storage::SessionStorage; + +/// Generic policy interface used by the account kernel and related subsystems. +pub trait Policy { + fn evaluate(&self, env: &Env, context: &C) -> Result<(), AccountError>; +} + +pub struct IntentContext<'a> { + pub account: &'a Address, + pub intent: &'a SignedIntent, +} + +pub struct SessionContext<'a> { + pub account: &'a Address, + pub intent: &'a SignedIntent, +} + +pub struct DeviceContext<'a> { + pub account: &'a Address, + pub key_id: &'a BytesN<32>, +} + +pub struct RecoveryContext<'a> { + pub account: &'a Address, + pub guardian: &'a Address, +} + +/// Reject expired intents. +pub struct IntentExpiryPolicy; + +impl Policy> for IntentExpiryPolicy { + fn evaluate(&self, env: &Env, context: &IntentContext<'_>) -> Result<(), AccountError> { + if env.ledger().timestamp() >= context.intent.expires_at { + return Err(AccountError::IntentExpired); + } + Ok(()) + } +} + +/// Enforces session existence, expiry, scope, budget and nonce. +pub struct SessionPolicy; + +impl Policy> for SessionPolicy { + fn evaluate(&self, env: &Env, context: &SessionContext<'_>) -> Result<(), AccountError> { + let session = + SessionStorage::load(env, context.account, &context.intent.signer.session_key_id) + .ok_or(AccountError::SessionRevoked)?; + + if env.ledger().timestamp() >= session.scope.expires_at { + return Err(AccountError::SessionExpired); + } + if session.operations_used >= session.scope.max_operations { + return Err(AccountError::SessionBudgetExceeded); + } + if session.next_nonce != context.intent.nonce { + return Err(AccountError::NonceMismatch); + } + if !SessionStorage::is_action_allowed(&session.scope, &context.intent.action.system_name) { + return Err(AccountError::ActionNotAllowed); + } + Ok(()) + } +} + +/// Ensures a bound device key is active under the account's device policy. +pub struct ActiveDevicePolicy; + +impl Policy> for ActiveDevicePolicy { + fn evaluate(&self, env: &Env, context: &DeviceContext<'_>) -> Result<(), AccountError> { + let devices = DeviceStorage::load_devices(env, context.account); + for i in 0..devices.len() { + if let Some(device) = devices.get(i) { + if &device.key_id == context.key_id && device.is_active { + return Ok(()); + } + } + } + Err(AccountError::DeviceNotFound) + } +} + +/// Ensures an address is currently configured as a guardian. +pub struct GuardianPolicy; + +impl Policy> for GuardianPolicy { + fn evaluate(&self, env: &Env, context: &RecoveryContext<'_>) -> Result<(), AccountError> { + let guardians = RecoveryStorage::load_guardians(env, context.account); + for i in 0..guardians.len() { + if let Some(guardian) = guardians.get(i) { + if &guardian.address == context.guardian { + return Ok(()); + } + } + } + Err(AccountError::Unauthorized) + } +} diff --git a/src/accounts/replay.rs b/src/accounts/replay.rs new file mode 100644 index 0000000..c52ac32 --- /dev/null +++ b/src/accounts/replay.rs @@ -0,0 +1,34 @@ +use soroban_sdk::{Address, Env, Symbol}; + +use super::error::AccountError; + +const ACCOUNT_NONCE_PREFIX: &str = "acct_nonce"; + +/// Persistent nonce tracking for replay protection. +pub struct ReplayProtection; + +impl ReplayProtection { + pub fn next_account_nonce(env: &Env, account: &Address) -> u64 { + let key = Self::storage_key(env, account); + env.storage().persistent().get(&key).unwrap_or(0) + } + + pub fn verify_and_consume_account_nonce( + env: &Env, + account: &Address, + nonce: u64, + ) -> Result { + let expected = Self::next_account_nonce(env, account); + if nonce != expected { + return Err(AccountError::NonceMismatch); + } + env.storage() + .persistent() + .set(&Self::storage_key(env, account), &(expected + 1)); + Ok(expected) + } + + fn storage_key(env: &Env, account: &Address) -> (Symbol, Address) { + (Symbol::new(env, ACCOUNT_NONCE_PREFIX), account.clone()) + } +} diff --git a/src/accounts/secp256r1_auth.rs b/src/accounts/secp256r1_auth.rs index d81e668..464607f 100644 --- a/src/accounts/secp256r1_auth.rs +++ b/src/accounts/secp256r1_auth.rs @@ -7,17 +7,24 @@ //! available in Protocol 21. //! //! # Example -//! ```ignore -//! // Register a passkey +//! ```no_run +//! use cougr_core::accounts::{verify_secp256r1, Secp256r1Key, Secp256r1Storage}; +//! use soroban_sdk::{symbol_short, testutils::Address as _, Address, Bytes, BytesN, Env}; +//! +//! let env = Env::default(); +//! let account_addr = Address::generate(&env); +//! let passkey_pubkey = BytesN::from_array(&env, &[4u8; 65]); //! let key = Secp256r1Key { -//! public_key: passkey_pubkey, +//! public_key: passkey_pubkey.clone(), //! label: symbol_short!("passkey1"), -//! registered_at: env.ledger().timestamp(), +//! registered_at: 0, //! }; //! Secp256r1Storage::store(&env, &account_addr, &key); //! -//! // Verify a signature +//! let message = Bytes::new(&env); +//! let signature = BytesN::from_array(&env, &[0u8; 64]); //! verify_secp256r1(&env, &passkey_pubkey, &message, &signature)?; +//! # Ok::<(), cougr_core::accounts::AccountError>(()) //! ``` use soroban_sdk::{contracttype, Address, Bytes, BytesN, Env, Symbol, Vec}; diff --git a/src/accounts/session_builder.rs b/src/accounts/session_builder.rs index 629d6cc..47fa23d 100644 --- a/src/accounts/session_builder.rs +++ b/src/accounts/session_builder.rs @@ -1,13 +1,18 @@ //! Fluent builder pattern for creating session keys with scoped permissions. //! //! # Example -//! ```ignore +//! ```no_run +//! use cougr_core::accounts::SessionBuilder; +//! use soroban_sdk::{symbol_short, Env}; +//! +//! let env = Env::default(); //! let scope = SessionBuilder::new(&env) //! .allow_action(symbol_short!("move")) //! .allow_action(symbol_short!("attack")) -//! .max_operations(100) -//! .expires_at(env.ledger().timestamp() + 3600) +//! .max_operations(100_u32) +//! .expires_at(3_600_u64) //! .build_scope(); +//! assert_eq!(scope.allowed_actions.len(), 2); //! ``` use soroban_sdk::{Env, Symbol, Vec}; diff --git a/src/accounts/signer.rs b/src/accounts/signer.rs new file mode 100644 index 0000000..a9188f0 --- /dev/null +++ b/src/accounts/signer.rs @@ -0,0 +1,73 @@ +use soroban_sdk::{Address, Env}; + +use super::error::AccountError; +use super::intent::{IntentProofKind, IntentSigner, SignedIntent}; +use super::secp256r1_auth::{verify_secp256r1, Secp256r1Storage}; + +/// Base signer verification interface for the account kernel. +pub trait AccountSigner { + fn verify( + &self, + env: &Env, + account: &Address, + intent: &SignedIntent, + ) -> Result<(), AccountError>; +} + +/// Direct owner signer backed by Soroban `require_auth`. +pub struct DirectAuthSigner; + +impl AccountSigner for DirectAuthSigner { + fn verify( + &self, + _env: &Env, + account: &Address, + intent: &SignedIntent, + ) -> Result<(), AccountError> { + if intent.signer.kind != IntentSigner::Direct { + return Err(AccountError::SignerMismatch); + } + account.require_auth(); + Ok(()) + } +} + +/// Session signer used for explicit session intents. +pub struct SessionAuthSigner; + +impl AccountSigner for SessionAuthSigner { + fn verify( + &self, + _env: &Env, + _account: &Address, + intent: &SignedIntent, + ) -> Result<(), AccountError> { + if intent.signer.kind != IntentSigner::Session { + return Err(AccountError::SignerMismatch); + } + Ok(()) + } +} + +/// Passkey signer backed by stored secp256r1 keys. +pub struct Secp256r1PasskeySigner; + +impl AccountSigner for Secp256r1PasskeySigner { + fn verify( + &self, + env: &Env, + account: &Address, + intent: &SignedIntent, + ) -> Result<(), AccountError> { + if intent.signer.kind != IntentSigner::Passkey { + return Err(AccountError::SignerMismatch); + } + if intent.proof.kind != IntentProofKind::Secp256r1 { + return Err(AccountError::InvalidSignature); + } + let key = Secp256r1Storage::find_by_label(env, account, &intent.signer.label) + .ok_or(AccountError::SignerNotRegistered)?; + let message = intent.action_hash.to_bytes(); + verify_secp256r1(env, &key.public_key, &message, &intent.proof.signature) + } +} diff --git a/src/accounts/storage.rs b/src/accounts/storage.rs index 009e743..66d1fe7 100644 --- a/src/accounts/storage.rs +++ b/src/accounts/storage.rs @@ -13,12 +13,28 @@ const SESSION_KEYS_PREFIX: &str = "sess_keys"; /// contract invocations (unlike `ContractAccount`'s in-memory storage). /// /// # Example -/// ```ignore -/// // Store a session key -/// SessionStorage::store(&env, &player_address, &session_key); +/// ```no_run +/// use cougr_core::accounts::{SessionKey, SessionScope, SessionStorage}; +/// use soroban_sdk::{symbol_short, testutils::Address as _, Address, BytesN, Env, Vec}; +/// +/// let env = Env::default(); +/// let player_address = Address::generate(&env); +/// let key_id = BytesN::from_array(&env, &[7u8; 32]); +/// let session_key = SessionKey { +/// key_id: key_id.clone(), +/// scope: SessionScope { +/// allowed_actions: Vec::from_array(&env, [symbol_short!("move")]), +/// max_operations: 10, +/// expires_at: 1_000, +/// }, +/// created_at: 0, +/// operations_used: 0, +/// next_nonce: 0, +/// }; /// -/// // Load it back -/// let key = SessionStorage::load(&env, &player_address, &key_id); +/// SessionStorage::store(&env, &player_address, &session_key); +/// let loaded = SessionStorage::load(&env, &player_address, &key_id); +/// assert!(loaded.is_some()); /// ``` pub struct SessionStorage; @@ -122,6 +138,39 @@ impl SessionStorage { Ok(()) } + /// Consume one authorized session action and advance its nonce. + pub fn consume_authorized_session( + env: &Env, + account: &Address, + key_id: &BytesN<32>, + ) -> Result { + let keys = Self::load_all(env, account); + let mut new_keys: Vec = Vec::new(env); + let mut updated: Option = None; + + for i in 0..keys.len() { + if let Some(mut k) = keys.get(i) { + if &k.key_id == key_id { + if env.ledger().timestamp() >= k.scope.expires_at { + return Err(AccountError::SessionExpired); + } + if k.operations_used >= k.scope.max_operations { + return Err(AccountError::SessionBudgetExceeded); + } + k.operations_used += 1; + k.next_nonce += 1; + updated = Some(k.clone()); + } + new_keys.push_back(k); + } + } + + let updated = updated.ok_or(AccountError::SessionRevoked)?; + let storage_key = Self::storage_key(env, account); + env.storage().persistent().set(&storage_key, &new_keys); + Ok(updated) + } + /// Remove all expired session keys for an account. /// Returns the number of keys removed. pub fn cleanup_expired(env: &Env, account: &Address) -> u32 { @@ -156,6 +205,21 @@ impl SessionStorage { fn storage_key(env: &Env, account: &Address) -> (Symbol, Address) { (Symbol::new(env, SESSION_KEYS_PREFIX), account.clone()) } + + pub(crate) fn is_action_allowed( + scope: &crate::accounts::types::SessionScope, + action: &Symbol, + ) -> bool { + if scope.allowed_actions.is_empty() { + return true; + } + for i in 0..scope.allowed_actions.len() { + if scope.allowed_actions.get(i).unwrap() == *action { + return true; + } + } + false + } } #[cfg(test)] @@ -182,6 +246,7 @@ mod tests { }, created_at: 0, operations_used: 0, + next_nonce: 0, } } diff --git a/src/accounts/testing.rs b/src/accounts/testing.rs index 41ec0e0..99c51f9 100644 --- a/src/accounts/testing.rs +++ b/src/accounts/testing.rs @@ -1,7 +1,8 @@ use soroban_sdk::{Address, Env}; use super::error::AccountError; -use super::traits::CougrAccount; +use super::intent::{AuthMethod, AuthResult, SignedIntent}; +use super::traits::{CougrAccount, IntentAccount}; use super::types::{AccountCapabilities, GameAction}; /// A mock account for testing that always authorizes actions. @@ -52,6 +53,25 @@ impl CougrAccount for MockAccount { } } +impl IntentAccount for MockAccount { + fn authorize_intent( + &mut self, + _env: &Env, + intent: &SignedIntent, + ) -> Result { + Ok(AuthResult { + method: match intent.signer.kind { + super::intent::IntentSigner::Direct => AuthMethod::Direct, + super::intent::IntentSigner::Session => AuthMethod::Session, + super::intent::IntentSigner::Passkey => AuthMethod::Passkey, + }, + nonce_consumed: intent.nonce, + session_key_id: intent.signer.session_key_id.clone(), + remaining_operations: 0, + }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/accounts/traits.rs b/src/accounts/traits.rs index 0c3bade..e78f51e 100644 --- a/src/accounts/traits.rs +++ b/src/accounts/traits.rs @@ -1,6 +1,7 @@ use soroban_sdk::{Address, BytesN, Env}; use super::error::AccountError; +use super::intent::{AuthResult, SignedIntent}; use super::types::{AccountCapabilities, GameAction, SessionKey, SessionScope}; /// Core account trait for Cougr game accounts. @@ -21,6 +22,15 @@ pub trait CougrAccount { fn authorize(&self, env: &Env, action: &GameAction) -> Result<(), AccountError>; } +/// Explicit intent-based authorization interface used by the account kernel. +pub trait IntentAccount: CougrAccount { + fn authorize_intent( + &mut self, + env: &Env, + intent: &SignedIntent, + ) -> Result; +} + /// Session key management for contract accounts. /// /// This trait provides session key functionality for gasless gameplay. diff --git a/src/accounts/types.rs b/src/accounts/types.rs index 093481f..c118625 100644 --- a/src/accounts/types.rs +++ b/src/accounts/types.rs @@ -2,7 +2,7 @@ use soroban_sdk::{contracttype, Bytes, BytesN, Symbol, Vec}; /// A game action that can be authorized by an account. #[contracttype] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct GameAction { pub system_name: Symbol, pub data: Bytes, @@ -25,6 +25,7 @@ pub struct SessionKey { pub scope: SessionScope, pub created_at: u64, pub operations_used: u32, + pub next_nonce: u64, } /// Capabilities supported by an account. @@ -37,15 +38,6 @@ pub struct AccountCapabilities { pub has_passkey_auth: bool, } -/// Authentication method variants. -#[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] -#[repr(u32)] -pub enum AuthMethod { - Ed25519 = 0, - Secp256r1 = 1, -} - #[cfg(test)] mod tests { use super::*; @@ -85,11 +77,6 @@ mod tests { assert!(!caps.has_session_keys); } - #[test] - fn test_auth_method() { - assert_ne!(AuthMethod::Ed25519, AuthMethod::Secp256r1); - } - #[test] fn test_session_key_creation() { let env = Env::default(); @@ -102,6 +89,7 @@ mod tests { }, created_at: 500, operations_used: 0, + next_nonce: 0, }; assert_eq!(key.operations_used, 0); assert_eq!(key.created_at, 500); diff --git a/src/archetype_world/archetype.rs b/src/archetype_world/archetype.rs index f1c1cc7..0ba9e29 100644 --- a/src/archetype_world/archetype.rs +++ b/src/archetype_world/archetype.rs @@ -18,13 +18,13 @@ pub type ArchetypeId = u32; #[derive(Clone, Debug)] pub struct Archetype { /// Unique archetype identifier. - pub id: ArchetypeId, + pub id: u32, /// Sorted list of component types in this archetype. pub component_types: Vec, /// List of entity IDs in this archetype. - pub entities: Vec, + pub entities: Vec, /// Component data keyed by (entity_id, component_type). - pub data: Map<(EntityId, Symbol), Bytes>, + pub data: Map<(u32, Symbol), Bytes>, } impl Archetype { diff --git a/src/archetype_world/world.rs b/src/archetype_world/world.rs index 4b29b48..77a7190 100644 --- a/src/archetype_world/world.rs +++ b/src/archetype_world/world.rs @@ -25,15 +25,15 @@ use soroban_sdk::{contracttype, Bytes, Env, Map, Symbol, Vec}; #[derive(Clone, Debug)] pub struct ArchetypeWorld { /// Next entity ID to assign. - pub next_entity_id: EntityId, + pub next_entity_id: u32, /// Next archetype ID to assign. - pub next_archetype_id: ArchetypeId, + pub next_archetype_id: u32, /// All archetypes, keyed by ID. - pub archetypes: Map, + pub archetypes: Map, /// Maps sorted component type lists to archetype IDs. - pub archetype_index: Map, ArchetypeId>, + pub archetype_index: Map, u32>, /// Maps each entity to its current archetype. - pub entity_archetype: Map, + pub entity_archetype: Map, /// Version counter for cache invalidation. pub version: u64, } diff --git a/src/change_tracker.rs b/src/change_tracker.rs index b8f8084..8a7d07d 100644 --- a/src/change_tracker.rs +++ b/src/change_tracker.rs @@ -10,18 +10,21 @@ use soroban_sdk::{Bytes, Symbol}; /// skip unchanged entities, improving performance. /// /// # Example -/// ```ignore +/// ``` +/// use cougr_core::change_tracker::ChangeTracker; +/// use soroban_sdk::symbol_short; +/// +/// let entity_id = 1; /// let mut tracker = ChangeTracker::new(); /// tracker.record_add(entity_id, symbol_short!("pos")); /// -/// // Later, query what changed: /// if tracker.was_added(entity_id, &symbol_short!("pos")) { -/// // handle newly added position +/// assert_eq!(tracker.change_count(), 1); /// } /// -/// // Clear at end of tick: /// tracker.clear(); /// tracker.advance_tick(); +/// assert_eq!(tracker.tick(), 1); /// ``` pub struct ChangeTracker { added: Vec<(EntityId, Symbol)>, @@ -139,13 +142,17 @@ impl Default for ChangeTracker { /// in a `ChangeTracker` for later querying by systems. /// /// # Example -/// ```ignore +/// ``` +/// use cougr_core::change_tracker::TrackedWorld; +/// use cougr_core::simple_world::SimpleWorld; +/// use soroban_sdk::{symbol_short, Bytes, Env}; +/// /// let env = Env::default(); /// let world = SimpleWorld::new(&env); /// let mut tracked = TrackedWorld::new(world); /// /// let e1 = tracked.spawn_entity(); -/// tracked.add_component(e1, symbol_short!("pos"), data); +/// tracked.add_component(e1, symbol_short!("pos"), Bytes::new(&env)); /// /// assert!(tracked.tracker().was_added(e1, &symbol_short!("pos"))); /// ``` diff --git a/src/commands.rs b/src/commands.rs index 2919538..d164779 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -26,17 +26,22 @@ struct CommandEntry { /// systems can queue commands and apply them after iteration completes. /// /// # Example -/// ```ignore -/// fn spawn_bullets_system(world: &mut SimpleWorld, env: &Env) { -/// let mut commands = CommandQueue::new(); +/// ``` +/// use cougr_core::commands::CommandQueue; +/// use cougr_core::simple_world::SimpleWorld; +/// use soroban_sdk::{symbol_short, Bytes, Env}; +/// +/// let env = Env::default(); +/// let mut world = SimpleWorld::new(&env); +/// let mut commands = CommandQueue::new(); /// -/// // ... iterate entities, decide to spawn bullets ... -/// commands.spawn(); -/// commands.add_component(0, symbol_short!("bullet"), data); // entity 0 = placeholder +/// commands.spawn(); +/// let spawned = commands.apply(&mut world); +/// commands = CommandQueue::new(); +/// commands.add_component(spawned[0], symbol_short!("bullet"), Bytes::new(&env)); +/// commands.apply(&mut world); /// -/// // Apply all queued changes at once -/// let spawned = commands.apply(world); -/// } +/// assert!(world.has_component(spawned[0], &symbol_short!("bullet"))); /// ``` pub struct CommandQueue { commands: Vec, diff --git a/src/components.rs b/src/components.rs deleted file mode 100644 index bc46e99..0000000 --- a/src/components.rs +++ /dev/null @@ -1,8 +0,0 @@ -use soroban_sdk::contracttype; - -#[contracttype] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Position { - pub x: u32, - pub y: u32, -} diff --git a/src/debug/introspect.rs b/src/debug/introspect.rs index 1449023..8a5734b 100644 --- a/src/debug/introspect.rs +++ b/src/debug/introspect.rs @@ -9,7 +9,7 @@ use soroban_sdk::{contracttype, Env, Symbol}; #[derive(Clone, Debug)] pub struct EntitySummary { /// The entity's ID. - pub entity_id: EntityId, + pub entity_id: u32, /// Total number of components (table + sparse). pub component_count: u32, /// List of component type symbols. @@ -35,7 +35,7 @@ pub struct WorldSummary { /// Current world version. pub version: u64, /// Next entity ID that will be assigned. - pub next_entity_id: EntityId, + pub next_entity_id: u32, } /// Inspect a single entity, returning a summary of its components. diff --git a/src/debug/snapshot.rs b/src/debug/snapshot.rs index 612424c..48d2cf0 100644 --- a/src/debug/snapshot.rs +++ b/src/debug/snapshot.rs @@ -15,7 +15,7 @@ pub struct WorldSnapshot { /// Number of entities. pub entity_count: u32, /// Map from entity ID to a list of (component_type, data) pairs. - pub entity_states: Map>, + pub entity_states: Map>, } /// Diff between two snapshots (runtime-only, uses `alloc::vec::Vec`). @@ -34,7 +34,7 @@ pub struct WorldDiff { /// Take a snapshot of the current world state. pub fn take_snapshot(world: &SimpleWorld, env: &Env) -> WorldSnapshot { - let mut entity_states: Map> = Map::new(env); + let mut entity_states: Map> = Map::new(env); for eid in world.entity_components.keys().iter() { if let Some(types) = world.entity_components.get(eid) { diff --git a/src/event/base.rs b/src/event/base.rs deleted file mode 100644 index 52839f3..0000000 --- a/src/event/base.rs +++ /dev/null @@ -1,423 +0,0 @@ -use crate::change_detection::MaybeLocation; -use crate::component::ComponentId; -use crate::world::World; -use crate::{component::Component, traversal::Traversal}; -#[cfg(feature = "bevy_reflect")] -use bevy_reflect::Reflect; -use core::{ - cmp::Ordering, - fmt, - hash::{Hash, Hasher}, - marker::PhantomData, -}; - -/// Something that "happens" and can be processed by app logic. -/// -/// Events can be triggered on a [`World`] using a method like [`trigger`](World::trigger), -/// causing any global [`Observer`] watching that event to run. This allows for push-based -/// event handling where observers are immediately notified of events as they happen. -/// -/// Additional event handling behavior can be enabled by implementing the [`EntityEvent`] -/// and [`BufferedEvent`] traits: -/// -/// - [`EntityEvent`]s support targeting specific entities, triggering any observers watching those targets. -/// They are useful for entity-specific event handlers and can even be propagated from one entity to another. -/// - [`BufferedEvent`]s support a pull-based event handling system where events are written using an [`EventWriter`] -/// and read later using an [`EventReader`]. This is an alternative to observers that allows efficient batch processing -/// of events at fixed points in a schedule. -/// -/// Events must be thread-safe. -/// -/// # Usage -/// -/// The [`Event`] trait can be derived: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// #[derive(Event)] -/// struct Speak { -/// message: String, -/// } -/// ``` -/// -/// An [`Observer`] can then be added to listen for this event type: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Event)] -/// # struct Speak { -/// # message: String, -/// # } -/// # -/// # let mut world = World::new(); -/// # -/// world.add_observer(|trigger: On| { -/// println!("{}", trigger.message); -/// }); -/// ``` -/// -/// The event can be triggered on the [`World`] using the [`trigger`](World::trigger) method: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Event)] -/// # struct Speak { -/// # message: String, -/// # } -/// # -/// # let mut world = World::new(); -/// # -/// # world.add_observer(|trigger: On| { -/// # println!("{}", trigger.message); -/// # }); -/// # -/// # world.flush(); -/// # -/// world.trigger(Speak { -/// message: "Hello!".to_string(), -/// }); -/// ``` -/// -/// For events that additionally need entity targeting or buffering, consider also deriving -/// [`EntityEvent`] or [`BufferedEvent`], respectively. -/// -/// [`World`]: crate::world::World -/// [`Observer`]: crate::observer::Observer -/// [`EventReader`]: super::EventReader -/// [`EventWriter`]: super::EventWriter -#[diagnostic::on_unimplemented( - message = "`{Self}` is not an `Event`", - label = "invalid `Event`", - note = "consider annotating `{Self}` with `#[derive(Event)]`" -)] -pub trait Event: Send + Sync + 'static { - /// Generates the [`ComponentId`] for this event type. - /// - /// If this type has already been registered, - /// this will return the existing [`ComponentId`]. - /// - /// This is used by various dynamically typed observer APIs, - /// such as [`World::trigger_targets_dynamic`]. - /// - /// # Warning - /// - /// This method should not be overridden by implementers, - /// and should always correspond to the implementation of [`component_id`](Event::component_id). - fn register_component_id(world: &mut World) -> ComponentId { - world.register_component::>() - } - - /// Fetches the [`ComponentId`] for this event type, - /// if it has already been generated. - /// - /// This is used by various dynamically typed observer APIs, - /// such as [`World::trigger_targets_dynamic`]. - /// - /// # Warning - /// - /// This method should not be overridden by implementers, - /// and should always correspond to the implementation of [`register_component_id`](Event::register_component_id). - fn component_id(world: &World) -> Option { - world.component_id::>() - } -} - -/// An [`Event`] that can be targeted at specific entities. -/// -/// Entity events can be triggered on a [`World`] with specific entity targets using a method -/// like [`trigger_targets`](World::trigger_targets), causing any [`Observer`] watching the event -/// for those entities to run. -/// -/// Unlike basic [`Event`]s, entity events can optionally be propagated from one entity target to another -/// based on the [`EntityEvent::Traversal`] type associated with the event. This enables use cases -/// such as bubbling events to parent entities for UI purposes. -/// -/// Entity events must be thread-safe. -/// -/// # Usage -/// -/// The [`EntityEvent`] trait can be derived. The `event` attribute can be used to further configure -/// the propagation behavior: adding `auto_propagate` sets [`EntityEvent::AUTO_PROPAGATE`] to `true`, -/// while adding `traversal = X` sets [`EntityEvent::Traversal`] to be of type `X`. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// // When the `Damage` event is triggered on an entity, bubble the event up to ancestors. -/// #[derive(Event, EntityEvent)] -/// #[entity_event(traversal = &'static ChildOf, auto_propagate)] -/// struct Damage { -/// amount: f32, -/// } -/// ``` -/// -/// An [`Observer`] can then be added to listen for this event type for the desired entity: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Event, EntityEvent)] -/// # #[entity_event(traversal = &'static ChildOf, auto_propagate)] -/// # struct Damage { -/// # amount: f32, -/// # } -/// # -/// # #[derive(Component)] -/// # struct Health(f32); -/// # -/// # #[derive(Component)] -/// # struct Enemy; -/// # -/// # #[derive(Component)] -/// # struct ArmorPiece; -/// # -/// # let mut world = World::new(); -/// # -/// // Spawn an enemy entity. -/// let enemy = world.spawn((Enemy, Health(100.0))).id(); -/// -/// // Spawn some armor as a child of the enemy entity. -/// // When the armor takes damage, it will bubble the event up to the enemy, -/// // which can then handle the event with its own observer. -/// let armor_piece = world -/// .spawn((ArmorPiece, Health(25.0), ChildOf(enemy))) -/// .observe(|trigger: On, mut query: Query<&mut Health>| { -/// // Note: `On::target` only exists because this is an `EntityEvent`. -/// let mut health = query.get_mut(trigger.target()).unwrap(); -/// health.0 -= trigger.amount; -/// }) -/// .id(); -/// ``` -/// -/// The event can be triggered on the [`World`] using the [`trigger_targets`](World::trigger_targets) method, -/// providing the desired entity target(s): -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Event, EntityEvent)] -/// # #[entity_event(traversal = &'static ChildOf, auto_propagate)] -/// # struct Damage { -/// # amount: f32, -/// # } -/// # -/// # #[derive(Component)] -/// # struct Health(f32); -/// # -/// # #[derive(Component)] -/// # struct Enemy; -/// # -/// # #[derive(Component)] -/// # struct ArmorPiece; -/// # -/// # let mut world = World::new(); -/// # -/// # let enemy = world.spawn((Enemy, Health(100.0))).id(); -/// # let armor_piece = world -/// # .spawn((ArmorPiece, Health(25.0), ChildOf(enemy))) -/// # .observe(|trigger: On, mut query: Query<&mut Health>| { -/// # // Note: `On::target` only exists because this is an `EntityEvent`. -/// # let mut health = query.get_mut(trigger.target()).unwrap(); -/// # health.0 -= trigger.amount; -/// # }) -/// # .id(); -/// # -/// # world.flush(); -/// # -/// world.trigger_targets(Damage { amount: 10.0 }, armor_piece); -/// ``` -/// -/// [`World`]: crate::world::World -/// [`TriggerTargets`]: crate::observer::TriggerTargets -/// [`Observer`]: crate::observer::Observer -/// [`Events`]: super::Events -/// [`EventReader`]: super::EventReader -/// [`EventWriter`]: super::EventWriter -#[diagnostic::on_unimplemented( - message = "`{Self}` is not an `EntityEvent`", - label = "invalid `EntityEvent`", - note = "consider annotating `{Self}` with `#[derive(Event, EntityEvent)]`" -)] -pub trait EntityEvent: Event { - /// The component that describes which [`Entity`] to propagate this event to next, when [propagation] is enabled. - /// - /// [`Entity`]: crate::entity::Entity - /// [propagation]: crate::observer::On::propagate - type Traversal: Traversal; - - /// When true, this event will always attempt to propagate when [triggered], without requiring a call - /// to [`On::propagate`]. - /// - /// [triggered]: crate::system::Commands::trigger_targets - /// [`On::propagate`]: crate::observer::On::propagate - const AUTO_PROPAGATE: bool = false; -} - -/// A buffered [`Event`] for pull-based event handling. -/// -/// Buffered events can be written with [`EventWriter`] and read using the [`EventReader`] system parameter. -/// These events are stored in the [`Events`] resource, and require periodically polling the world for new events, -/// typically in a system that runs as part of a schedule. -/// -/// While the polling imposes a small overhead, buffered events are useful for efficiently batch processing -/// a large number of events at once. This can make them more efficient than [`Event`]s used by [`Observer`]s -/// for events that happen at a high frequency or in large quantities. -/// -/// Unlike [`Event`]s triggered for observers, buffered events are evaluated at fixed points in the schedule -/// rather than immediately when they are sent. This allows for more predictable scheduling and deferring -/// event processing to a later point in time. -/// -/// Buffered events do *not* trigger observers automatically when they are written via an [`EventWriter`]. -/// However, they can still also be triggered on a [`World`] in case you want both buffered and immediate -/// event handling for the same event. -/// -/// Buffered events must be thread-safe. -/// -/// # Usage -/// -/// The [`BufferedEvent`] trait can be derived: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// #[derive(Event, BufferedEvent)] -/// struct Message(String); -/// ``` -/// -/// The event can then be written to the event buffer using an [`EventWriter`]: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Event, BufferedEvent)] -/// # struct Message(String); -/// # -/// fn write_hello(mut writer: EventWriter) { -/// writer.write(Message("Hello!".to_string())); -/// } -/// ``` -/// -/// Buffered events can be efficiently read using an [`EventReader`]: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Event, BufferedEvent)] -/// # struct Message(String); -/// # -/// fn read_messages(mut reader: EventReader) { -/// // Process all buffered events of type `Message`. -/// for Message(message) in reader.read() { -/// println!("{message}"); -/// } -/// } -/// ``` -/// -/// [`World`]: crate::world::World -/// [`Observer`]: crate::observer::Observer -/// [`Events`]: super::Events -/// [`EventReader`]: super::EventReader -/// [`EventWriter`]: super::EventWriter -#[diagnostic::on_unimplemented( - message = "`{Self}` is not an `BufferedEvent`", - label = "invalid `BufferedEvent`", - note = "consider annotating `{Self}` with `#[derive(Event, BufferedEvent)]`" -)] -pub trait BufferedEvent: Event {} - -/// An internal type that implements [`Component`] for a given [`Event`] type. -/// -/// This exists so we can easily get access to a unique [`ComponentId`] for each [`Event`] type, -/// without requiring that [`Event`] types implement [`Component`] directly. -/// [`ComponentId`] is used internally as a unique identifier for events because they are: -/// -/// - Unique to each event type. -/// - Can be quickly generated and looked up. -/// - Are compatible with dynamic event types, which aren't backed by a Rust type. -/// -/// This type is an implementation detail and should never be made public. -// TODO: refactor events to store their metadata on distinct entities, rather than using `ComponentId` -#[derive(Component)] -struct EventWrapperComponent(PhantomData); - -/// An `EventId` uniquely identifies an event stored in a specific [`World`]. -/// -/// An `EventId` can among other things be used to trace the flow of an event from the point it was -/// sent to the point it was processed. `EventId`s increase monotonically by send order. -/// -/// [`World`]: crate::world::World -#[cfg_attr( - feature = "bevy_reflect", - derive(Reflect), - reflect(Clone, Debug, PartialEq, Hash) -)] -pub struct EventId { - /// Uniquely identifies the event associated with this ID. - // This value corresponds to the order in which each event was added to the world. - pub id: usize, - /// The source code location that triggered this event. - pub caller: MaybeLocation, - #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))] - pub(super) _marker: PhantomData, -} - -impl Copy for EventId {} - -impl Clone for EventId { - fn clone(&self) -> Self { - *self - } -} - -impl fmt::Display for EventId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(self, f) - } -} - -impl fmt::Debug for EventId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "event<{}>#{}", - core::any::type_name::().split("::").last().unwrap(), - self.id, - ) - } -} - -impl PartialEq for EventId { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Eq for EventId {} - -impl PartialOrd for EventId { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for EventId { - fn cmp(&self, other: &Self) -> Ordering { - self.id.cmp(&other.id) - } -} - -impl Hash for EventId { - fn hash(&self, state: &mut H) { - Hash::hash(&self.id, state); - } -} - -#[derive(Debug)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -pub(crate) struct EventInstance { - pub event_id: EventId, - pub event: E, -} diff --git a/src/event/collections.rs b/src/event/collections.rs deleted file mode 100644 index 7d18541..0000000 --- a/src/event/collections.rs +++ /dev/null @@ -1,422 +0,0 @@ -use alloc::vec::Vec; -use bevy_ecs::{ - change_detection::MaybeLocation, - event::{BufferedEvent, EventCursor, EventId, EventInstance}, - resource::Resource, -}; -use core::{ - marker::PhantomData, - ops::{Deref, DerefMut}, -}; -#[cfg(feature = "bevy_reflect")] -use { - bevy_ecs::reflect::ReflectResource, - bevy_reflect::{std_traits::ReflectDefault, Reflect}, -}; - -/// An event collection that represents the events that occurred within the last two -/// [`Events::update`] calls. -/// Events can be written to using an [`EventWriter`] -/// and are typically cheaply read using an [`EventReader`]. -/// -/// Each event can be consumed by multiple systems, in parallel, -/// with consumption tracked by the [`EventReader`] on a per-system basis. -/// -/// If no [ordering](https://github.com/bevyengine/bevy/blob/main/examples/ecs/ecs_guide.rs) -/// is applied between writing and reading systems, there is a risk of a race condition. -/// This means that whether the events arrive before or after the next [`Events::update`] is unpredictable. -/// -/// This collection is meant to be paired with a system that calls -/// [`Events::update`] exactly once per update/frame. -/// -/// [`event_update_system`] is a system that does this, typically initialized automatically using -/// [`add_event`](https://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event). -/// [`EventReader`]s are expected to read events from this collection at least once per loop/frame. -/// Events will persist across a single frame boundary and so ordering of event producers and -/// consumers is not critical (although poorly-planned ordering may cause accumulating lag). -/// If events are not handled by the end of the frame after they are updated, they will be -/// dropped silently. -/// -/// # Example -/// -/// ``` -/// use bevy_ecs::event::{BufferedEvent, Event, Events}; -/// -/// #[derive(Event, BufferedEvent)] -/// struct MyEvent { -/// value: usize -/// } -/// -/// // setup -/// let mut events = Events::::default(); -/// let mut cursor = events.get_cursor(); -/// -/// // run this once per update/frame -/// events.update(); -/// -/// // somewhere else: send an event -/// events.send(MyEvent { value: 1 }); -/// -/// // somewhere else: read the events -/// for event in cursor.read(&events) { -/// assert_eq!(event.value, 1) -/// } -/// -/// // events are only processed once per reader -/// assert_eq!(cursor.read(&events).count(), 0); -/// ``` -/// -/// # Details -/// -/// [`Events`] is implemented using a variation of a double buffer strategy. -/// Each call to [`update`](Events::update) swaps buffers and clears out the oldest one. -/// - [`EventReader`]s will read events from both buffers. -/// - [`EventReader`]s that read at least once per update will never drop events. -/// - [`EventReader`]s that read once within two updates might still receive some events -/// - [`EventReader`]s that read after two updates are guaranteed to drop all events that occurred -/// before those updates. -/// -/// The buffers in [`Events`] will grow indefinitely if [`update`](Events::update) is never called. -/// -/// An alternative call pattern would be to call [`update`](Events::update) -/// manually across frames to control when events are cleared. -/// This complicates consumption and risks ever-expanding memory usage if not cleaned up, -/// but can be done by adding your event as a resource instead of using -/// [`add_event`](https://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event). -/// -/// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/event.rs) -/// [Example usage standalone.](https://github.com/bevyengine/bevy/blob/latest/crates/bevy_ecs/examples/events.rs) -/// -/// [`EventReader`]: super::EventReader -/// [`EventWriter`]: super::EventWriter -/// [`event_update_system`]: super::event_update_system -#[derive(Debug, Resource)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Resource, Default))] -pub struct Events { - /// Holds the oldest still active events. - /// Note that `a.start_event_count + a.len()` should always be equal to `events_b.start_event_count`. - pub(crate) events_a: EventSequence, - /// Holds the newer events. - pub(crate) events_b: EventSequence, - pub(crate) event_count: usize, -} - -// Derived Default impl would incorrectly require E: Default -impl Default for Events { - fn default() -> Self { - Self { - events_a: Default::default(), - events_b: Default::default(), - event_count: Default::default(), - } - } -} - -impl Events { - /// Returns the index of the oldest event stored in the event buffer. - pub fn oldest_event_count(&self) -> usize { - self.events_a.start_event_count - } - - /// "Sends" an `event` by writing it to the current event buffer. - /// [`EventReader`](super::EventReader)s can then read the event. - /// This method returns the [ID](`EventId`) of the sent `event`. - #[track_caller] - pub fn send(&mut self, event: E) -> EventId { - self.send_with_caller(event, MaybeLocation::caller()) - } - - pub(crate) fn send_with_caller(&mut self, event: E, caller: MaybeLocation) -> EventId { - let event_id = EventId { - id: self.event_count, - caller, - _marker: PhantomData, - }; - #[cfg(feature = "detailed_trace")] - tracing::trace!("Events::send() -> id: {}", event_id); - - let event_instance = EventInstance { event_id, event }; - - self.events_b.push(event_instance); - self.event_count += 1; - - event_id - } - - /// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s. - /// This is more efficient than sending each event individually. - /// This method returns the [IDs](`EventId`) of the sent `events`. - #[track_caller] - pub fn send_batch(&mut self, events: impl IntoIterator) -> SendBatchIds { - let last_count = self.event_count; - - self.extend(events); - - SendBatchIds { - last_count, - event_count: self.event_count, - _marker: PhantomData, - } - } - - /// Sends the default value of the event. Useful when the event is an empty struct. - /// This method returns the [ID](`EventId`) of the sent `event`. - #[track_caller] - pub fn send_default(&mut self) -> EventId - where - E: Default, - { - self.send(Default::default()) - } - - /// Gets a new [`EventCursor`]. This will include all events already in the event buffers. - pub fn get_cursor(&self) -> EventCursor { - EventCursor::default() - } - - /// Gets a new [`EventCursor`]. This will ignore all events already in the event buffers. - /// It will read all future events. - pub fn get_cursor_current(&self) -> EventCursor { - EventCursor { - last_event_count: self.event_count, - ..Default::default() - } - } - - /// Swaps the event buffers and clears the oldest event buffer. In general, this should be - /// called once per frame/update. - /// - /// If you need access to the events that were removed, consider using [`Events::update_drain`]. - pub fn update(&mut self) { - core::mem::swap(&mut self.events_a, &mut self.events_b); - self.events_b.clear(); - self.events_b.start_event_count = self.event_count; - debug_assert_eq!( - self.events_a.start_event_count + self.events_a.len(), - self.events_b.start_event_count - ); - } - - /// Swaps the event buffers and drains the oldest event buffer, returning an iterator - /// of all events that were removed. In general, this should be called once per frame/update. - /// - /// If you do not need to take ownership of the removed events, use [`Events::update`] instead. - #[must_use = "If you do not need the returned events, call .update() instead."] - pub fn update_drain(&mut self) -> impl Iterator + '_ { - core::mem::swap(&mut self.events_a, &mut self.events_b); - let iter = self.events_b.events.drain(..); - self.events_b.start_event_count = self.event_count; - debug_assert_eq!( - self.events_a.start_event_count + self.events_a.len(), - self.events_b.start_event_count - ); - - iter.map(|e| e.event) - } - - #[inline] - fn reset_start_event_count(&mut self) { - self.events_a.start_event_count = self.event_count; - self.events_b.start_event_count = self.event_count; - } - - /// Removes all events. - #[inline] - pub fn clear(&mut self) { - self.reset_start_event_count(); - self.events_a.clear(); - self.events_b.clear(); - } - - /// Returns the number of events currently stored in the event buffer. - #[inline] - pub fn len(&self) -> usize { - self.events_a.len() + self.events_b.len() - } - - /// Returns true if there are no events currently stored in the event buffer. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Creates a draining iterator that removes all events. - pub fn drain(&mut self) -> impl Iterator + '_ { - self.reset_start_event_count(); - - // Drain the oldest events first, then the newest - self.events_a - .drain(..) - .chain(self.events_b.drain(..)) - .map(|i| i.event) - } - - /// Iterates over events that happened since the last "update" call. - /// WARNING: You probably don't want to use this call. In most cases you should use an - /// [`EventReader`]. You should only use this if you know you only need to consume events - /// between the last `update()` call and your call to `iter_current_update_events`. - /// If events happen outside that window, they will not be handled. For example, any events that - /// happen after this call and before the next `update()` call will be dropped. - /// - /// [`EventReader`]: super::EventReader - pub fn iter_current_update_events(&self) -> impl ExactSizeIterator { - self.events_b.iter().map(|i| &i.event) - } - - /// Get a specific event by id if it still exists in the events buffer. - pub fn get_event(&self, id: usize) -> Option<(&E, EventId)> { - if id < self.oldest_event_count() { - return None; - } - - let sequence = self.sequence(id); - let index = id.saturating_sub(sequence.start_event_count); - - sequence - .get(index) - .map(|instance| (&instance.event, instance.event_id)) - } - - /// Which event buffer is this event id a part of. - fn sequence(&self, id: usize) -> &EventSequence { - if id < self.events_b.start_event_count { - &self.events_a - } else { - &self.events_b - } - } -} - -impl Extend for Events { - #[track_caller] - fn extend(&mut self, iter: I) - where - I: IntoIterator, - { - let old_count = self.event_count; - let mut event_count = self.event_count; - let events = iter.into_iter().map(|event| { - let event_id = EventId { - id: event_count, - caller: MaybeLocation::caller(), - _marker: PhantomData, - }; - event_count += 1; - EventInstance { event_id, event } - }); - - self.events_b.extend(events); - - if old_count != event_count { - #[cfg(feature = "detailed_trace")] - tracing::trace!( - "Events::extend() -> ids: ({}..{})", - self.event_count, - event_count - ); - } - - self.event_count = event_count; - } -} - -#[derive(Debug)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Default))] -pub(crate) struct EventSequence { - pub(crate) events: Vec>, - pub(crate) start_event_count: usize, -} - -// Derived Default impl would incorrectly require E: Default -impl Default for EventSequence { - fn default() -> Self { - Self { - events: Default::default(), - start_event_count: Default::default(), - } - } -} - -impl Deref for EventSequence { - type Target = Vec>; - - fn deref(&self) -> &Self::Target { - &self.events - } -} - -impl DerefMut for EventSequence { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.events - } -} - -/// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch. -pub struct SendBatchIds { - last_count: usize, - event_count: usize, - _marker: PhantomData, -} - -impl Iterator for SendBatchIds { - type Item = EventId; - - fn next(&mut self) -> Option { - if self.last_count >= self.event_count { - return None; - } - - let result = Some(EventId { - id: self.last_count, - caller: MaybeLocation::caller(), - _marker: PhantomData, - }); - - self.last_count += 1; - - result - } -} - -impl ExactSizeIterator for SendBatchIds { - fn len(&self) -> usize { - self.event_count.saturating_sub(self.last_count) - } -} - -#[cfg(test)] -mod tests { - use crate::event::{BufferedEvent, Event, Events}; - - #[test] - fn iter_current_update_events_iterates_over_current_events() { - #[derive(Event, BufferedEvent, Clone)] - struct TestEvent; - - let mut test_events = Events::::default(); - - // Starting empty - assert_eq!(test_events.len(), 0); - assert_eq!(test_events.iter_current_update_events().count(), 0); - test_events.update(); - - // Sending one event - test_events.send(TestEvent); - - assert_eq!(test_events.len(), 1); - assert_eq!(test_events.iter_current_update_events().count(), 1); - test_events.update(); - - // Sending two events on the next frame - test_events.send(TestEvent); - test_events.send(TestEvent); - - assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3 - assert_eq!(test_events.iter_current_update_events().count(), 2); - test_events.update(); - - // Sending zero events - assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2 - assert_eq!(test_events.iter_current_update_events().count(), 0); - } -} diff --git a/src/event/event_cursor.rs b/src/event/event_cursor.rs deleted file mode 100644 index 70e19a7..0000000 --- a/src/event/event_cursor.rs +++ /dev/null @@ -1,141 +0,0 @@ -use bevy_ecs::event::{ - BufferedEvent, EventIterator, EventIteratorWithId, EventMutIterator, EventMutIteratorWithId, - Events, -}; -#[cfg(feature = "multi_threaded")] -use bevy_ecs::event::{EventMutParIter, EventParIter}; -use core::marker::PhantomData; - -/// Stores the state for an [`EventReader`] or [`EventMutator`]. -/// -/// Access to the [`Events`] resource is required to read any incoming events. -/// -/// In almost all cases, you should just use an [`EventReader`] or [`EventMutator`], -/// which will automatically manage the state for you. -/// -/// However, this type can be useful if you need to manually track events, -/// such as when you're attempting to send and receive events of the same type in the same system. -/// -/// # Example -/// -/// ``` -/// use bevy_ecs::prelude::*; -/// use bevy_ecs::event::{BufferedEvent, Events, EventCursor}; -/// -/// #[derive(Event, BufferedEvent, Clone, Debug)] -/// struct MyEvent; -/// -/// /// A system that both sends and receives events using a [`Local`] [`EventCursor`]. -/// fn send_and_receive_events( -/// // The `Local` `SystemParam` stores state inside the system itself, rather than in the world. -/// // `EventCursor` is the internal state of `EventMutator`, which tracks which events have been seen. -/// mut local_event_reader: Local>, -/// // We can access the `Events` resource mutably, allowing us to both read and write its contents. -/// mut events: ResMut>, -/// ) { -/// // We must collect the events to resend, because we can't mutate events while we're iterating over the events. -/// let mut events_to_resend = Vec::new(); -/// -/// for event in local_event_reader.read(&mut events) { -/// events_to_resend.push(event.clone()); -/// } -/// -/// for event in events_to_resend { -/// events.send(MyEvent); -/// } -/// } -/// -/// # bevy_ecs::system::assert_is_system(send_and_receive_events); -/// ``` -/// -/// [`EventReader`]: super::EventReader -/// [`EventMutator`]: super::EventMutator -#[derive(Debug)] -pub struct EventCursor { - pub(super) last_event_count: usize, - pub(super) _marker: PhantomData, -} - -impl Default for EventCursor { - fn default() -> Self { - EventCursor { - last_event_count: 0, - _marker: Default::default(), - } - } -} - -impl Clone for EventCursor { - fn clone(&self) -> Self { - EventCursor { - last_event_count: self.last_event_count, - _marker: PhantomData, - } - } -} - -impl EventCursor { - /// See [`EventReader::read`](super::EventReader::read) - pub fn read<'a>(&'a mut self, events: &'a Events) -> EventIterator<'a, E> { - self.read_with_id(events).without_id() - } - - /// See [`EventMutator::read`](super::EventMutator::read) - pub fn read_mut<'a>(&'a mut self, events: &'a mut Events) -> EventMutIterator<'a, E> { - self.read_mut_with_id(events).without_id() - } - - /// See [`EventReader::read_with_id`](super::EventReader::read_with_id) - pub fn read_with_id<'a>(&'a mut self, events: &'a Events) -> EventIteratorWithId<'a, E> { - EventIteratorWithId::new(self, events) - } - - /// See [`EventMutator::read_with_id`](super::EventMutator::read_with_id) - pub fn read_mut_with_id<'a>( - &'a mut self, - events: &'a mut Events, - ) -> EventMutIteratorWithId<'a, E> { - EventMutIteratorWithId::new(self, events) - } - - /// See [`EventReader::par_read`](super::EventReader::par_read) - #[cfg(feature = "multi_threaded")] - pub fn par_read<'a>(&'a mut self, events: &'a Events) -> EventParIter<'a, E> { - EventParIter::new(self, events) - } - - /// See [`EventMutator::par_read`](super::EventMutator::par_read) - #[cfg(feature = "multi_threaded")] - pub fn par_read_mut<'a>(&'a mut self, events: &'a mut Events) -> EventMutParIter<'a, E> { - EventMutParIter::new(self, events) - } - - /// See [`EventReader::len`](super::EventReader::len) - pub fn len(&self, events: &Events) -> usize { - // The number of events in this reader is the difference between the most recent event - // and the last event seen by it. This will be at most the number of events contained - // with the events (any others have already been dropped) - // TODO: Warn when there are dropped events, or return e.g. a `Result` - events - .event_count - .saturating_sub(self.last_event_count) - .min(events.len()) - } - - /// Amount of events we missed. - pub fn missed_events(&self, events: &Events) -> usize { - events - .oldest_event_count() - .saturating_sub(self.last_event_count) - } - - /// See [`EventReader::is_empty()`](super::EventReader::is_empty) - pub fn is_empty(&self, events: &Events) -> bool { - self.len(events) == 0 - } - - /// See [`EventReader::clear()`](super::EventReader::clear) - pub fn clear(&mut self, events: &Events) { - self.last_event_count = events.event_count; - } -} diff --git a/src/event/iterators.rs b/src/event/iterators.rs deleted file mode 100644 index c90aed2..0000000 --- a/src/event/iterators.rs +++ /dev/null @@ -1,281 +0,0 @@ -#[cfg(feature = "multi_threaded")] -use bevy_ecs::batching::BatchingStrategy; -use bevy_ecs::event::{BufferedEvent, EventCursor, EventId, EventInstance, Events}; -use core::{iter::Chain, slice::Iter}; - -/// An iterator that yields any unread events from an [`EventReader`](super::EventReader) or [`EventCursor`]. -#[derive(Debug)] -pub struct EventIterator<'a, E: BufferedEvent> { - iter: EventIteratorWithId<'a, E>, -} - -impl<'a, E: BufferedEvent> Iterator for EventIterator<'a, E> { - type Item = &'a E; - fn next(&mut self) -> Option { - self.iter.next().map(|(event, _)| event) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - - fn count(self) -> usize { - self.iter.count() - } - - fn last(self) -> Option - where - Self: Sized, - { - self.iter.last().map(|(event, _)| event) - } - - fn nth(&mut self, n: usize) -> Option { - self.iter.nth(n).map(|(event, _)| event) - } -} - -impl<'a, E: BufferedEvent> ExactSizeIterator for EventIterator<'a, E> { - fn len(&self) -> usize { - self.iter.len() - } -} - -/// An iterator that yields any unread events (and their IDs) from an [`EventReader`](super::EventReader) or [`EventCursor`]. -#[derive(Debug)] -pub struct EventIteratorWithId<'a, E: BufferedEvent> { - reader: &'a mut EventCursor, - chain: Chain>, Iter<'a, EventInstance>>, - unread: usize, -} - -impl<'a, E: BufferedEvent> EventIteratorWithId<'a, E> { - /// Creates a new iterator that yields any `events` that have not yet been seen by `reader`. - pub fn new(reader: &'a mut EventCursor, events: &'a Events) -> Self { - let a_index = reader - .last_event_count - .saturating_sub(events.events_a.start_event_count); - let b_index = reader - .last_event_count - .saturating_sub(events.events_b.start_event_count); - let a = events.events_a.get(a_index..).unwrap_or_default(); - let b = events.events_b.get(b_index..).unwrap_or_default(); - - let unread_count = a.len() + b.len(); - // Ensure `len` is implemented correctly - debug_assert_eq!(unread_count, reader.len(events)); - reader.last_event_count = events.event_count - unread_count; - // Iterate the oldest first, then the newer events - let chain = a.iter().chain(b.iter()); - - Self { - reader, - chain, - unread: unread_count, - } - } - - /// Iterate over only the events. - pub fn without_id(self) -> EventIterator<'a, E> { - EventIterator { iter: self } - } -} - -impl<'a, E: BufferedEvent> Iterator for EventIteratorWithId<'a, E> { - type Item = (&'a E, EventId); - fn next(&mut self) -> Option { - match self - .chain - .next() - .map(|instance| (&instance.event, instance.event_id)) - { - Some(item) => { - #[cfg(feature = "detailed_trace")] - tracing::trace!("EventReader::iter() -> {}", item.1); - self.reader.last_event_count += 1; - self.unread -= 1; - Some(item) - } - None => None, - } - } - - fn size_hint(&self) -> (usize, Option) { - self.chain.size_hint() - } - - fn count(self) -> usize { - self.reader.last_event_count += self.unread; - self.unread - } - - fn last(self) -> Option - where - Self: Sized, - { - let EventInstance { event_id, event } = self.chain.last()?; - self.reader.last_event_count += self.unread; - Some((event, *event_id)) - } - - fn nth(&mut self, n: usize) -> Option { - if let Some(EventInstance { event_id, event }) = self.chain.nth(n) { - self.reader.last_event_count += n + 1; - self.unread -= n + 1; - Some((event, *event_id)) - } else { - self.reader.last_event_count += self.unread; - self.unread = 0; - None - } - } -} - -impl<'a, E: BufferedEvent> ExactSizeIterator for EventIteratorWithId<'a, E> { - fn len(&self) -> usize { - self.unread - } -} - -/// A parallel iterator over `BufferedEvent`s. -#[cfg(feature = "multi_threaded")] -#[derive(Debug)] -pub struct EventParIter<'a, E: BufferedEvent> { - reader: &'a mut EventCursor, - slices: [&'a [EventInstance]; 2], - batching_strategy: BatchingStrategy, - #[cfg(not(target_arch = "wasm32"))] - unread: usize, -} - -#[cfg(feature = "multi_threaded")] -impl<'a, E: BufferedEvent> EventParIter<'a, E> { - /// Creates a new parallel iterator over `events` that have not yet been seen by `reader`. - pub fn new(reader: &'a mut EventCursor, events: &'a Events) -> Self { - let a_index = reader - .last_event_count - .saturating_sub(events.events_a.start_event_count); - let b_index = reader - .last_event_count - .saturating_sub(events.events_b.start_event_count); - let a = events.events_a.get(a_index..).unwrap_or_default(); - let b = events.events_b.get(b_index..).unwrap_or_default(); - - let unread_count = a.len() + b.len(); - // Ensure `len` is implemented correctly - debug_assert_eq!(unread_count, reader.len(events)); - reader.last_event_count = events.event_count - unread_count; - - Self { - reader, - slices: [a, b], - batching_strategy: BatchingStrategy::default(), - #[cfg(not(target_arch = "wasm32"))] - unread: unread_count, - } - } - - /// Changes the batching strategy used when iterating. - /// - /// For more information on how this affects the resultant iteration, see - /// [`BatchingStrategy`]. - pub fn batching_strategy(mut self, strategy: BatchingStrategy) -> Self { - self.batching_strategy = strategy; - self - } - - /// Runs the provided closure for each unread event in parallel. - /// - /// Unlike normal iteration, the event order is not guaranteed in any form. - /// - /// # Panics - /// If the [`ComputeTaskPool`] is not initialized. If using this from an event reader that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - pub fn for_each(self, func: FN) { - self.for_each_with_id(move |e, _| func(e)); - } - - /// Runs the provided closure for each unread event in parallel, like [`for_each`](Self::for_each), - /// but additionally provides the `EventId` to the closure. - /// - /// Note that the order of iteration is not guaranteed, but `EventId`s are ordered by send order. - /// - /// # Panics - /// If the [`ComputeTaskPool`] is not initialized. If using this from an event reader that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[cfg_attr( - target_arch = "wasm32", - expect(unused_mut, reason = "not mutated on this target") - )] - pub fn for_each_with_id) + Send + Sync + Clone>(mut self, func: FN) { - #[cfg(target_arch = "wasm32")] - { - self.into_iter().for_each(|(e, i)| func(e, i)); - } - - #[cfg(not(target_arch = "wasm32"))] - { - let pool = bevy_tasks::ComputeTaskPool::get(); - let thread_count = pool.thread_num(); - if thread_count <= 1 { - return self.into_iter().for_each(|(e, i)| func(e, i)); - } - - let batch_size = self - .batching_strategy - .calc_batch_size(|| self.len(), thread_count); - let chunks = self.slices.map(|s| s.chunks_exact(batch_size)); - let remainders = chunks.each_ref().map(core::slice::ChunksExact::remainder); - - pool.scope(|scope| { - for batch in chunks.into_iter().flatten().chain(remainders) { - let func = func.clone(); - scope.spawn(async move { - for event in batch { - func(&event.event, event.event_id); - } - }); - } - }); - - // Events are guaranteed to be read at this point. - self.reader.last_event_count += self.unread; - self.unread = 0; - } - } - - /// Returns the number of [`BufferedEvent`]s to be iterated. - pub fn len(&self) -> usize { - self.slices.iter().map(|s| s.len()).sum() - } - - /// Returns [`true`] if there are no events remaining in this iterator. - pub fn is_empty(&self) -> bool { - self.slices.iter().all(|x| x.is_empty()) - } -} - -#[cfg(feature = "multi_threaded")] -impl<'a, E: BufferedEvent> IntoIterator for EventParIter<'a, E> { - type IntoIter = EventIteratorWithId<'a, E>; - type Item = ::Item; - - fn into_iter(self) -> Self::IntoIter { - let EventParIter { - reader, - slices: [a, b], - .. - } = self; - let unread = a.len() + b.len(); - let chain = a.iter().chain(b); - EventIteratorWithId { - reader, - chain, - unread, - } - } -} diff --git a/src/event/mut_iterators.rs b/src/event/mut_iterators.rs deleted file mode 100644 index 3fa8378..0000000 --- a/src/event/mut_iterators.rs +++ /dev/null @@ -1,284 +0,0 @@ -#[cfg(feature = "multi_threaded")] -use bevy_ecs::batching::BatchingStrategy; -use bevy_ecs::event::{BufferedEvent, EventCursor, EventId, EventInstance, Events}; -use core::{iter::Chain, slice::IterMut}; - -/// An iterator that yields any unread events from an [`EventMutator`] or [`EventCursor`]. -/// -/// [`EventMutator`]: super::EventMutator -#[derive(Debug)] -pub struct EventMutIterator<'a, E: BufferedEvent> { - iter: EventMutIteratorWithId<'a, E>, -} - -impl<'a, E: BufferedEvent> Iterator for EventMutIterator<'a, E> { - type Item = &'a mut E; - fn next(&mut self) -> Option { - self.iter.next().map(|(event, _)| event) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - - fn count(self) -> usize { - self.iter.count() - } - - fn last(self) -> Option - where - Self: Sized, - { - self.iter.last().map(|(event, _)| event) - } - - fn nth(&mut self, n: usize) -> Option { - self.iter.nth(n).map(|(event, _)| event) - } -} - -impl<'a, E: BufferedEvent> ExactSizeIterator for EventMutIterator<'a, E> { - fn len(&self) -> usize { - self.iter.len() - } -} - -/// An iterator that yields any unread events (and their IDs) from an [`EventMutator`] or [`EventCursor`]. -/// -/// [`EventMutator`]: super::EventMutator -#[derive(Debug)] -pub struct EventMutIteratorWithId<'a, E: BufferedEvent> { - mutator: &'a mut EventCursor, - chain: Chain>, IterMut<'a, EventInstance>>, - unread: usize, -} - -impl<'a, E: BufferedEvent> EventMutIteratorWithId<'a, E> { - /// Creates a new iterator that yields any `events` that have not yet been seen by `mutator`. - pub fn new(mutator: &'a mut EventCursor, events: &'a mut Events) -> Self { - let a_index = mutator - .last_event_count - .saturating_sub(events.events_a.start_event_count); - let b_index = mutator - .last_event_count - .saturating_sub(events.events_b.start_event_count); - let a = events.events_a.get_mut(a_index..).unwrap_or_default(); - let b = events.events_b.get_mut(b_index..).unwrap_or_default(); - - let unread_count = a.len() + b.len(); - - mutator.last_event_count = events.event_count - unread_count; - // Iterate the oldest first, then the newer events - let chain = a.iter_mut().chain(b.iter_mut()); - - Self { - mutator, - chain, - unread: unread_count, - } - } - - /// Iterate over only the events. - pub fn without_id(self) -> EventMutIterator<'a, E> { - EventMutIterator { iter: self } - } -} - -impl<'a, E: BufferedEvent> Iterator for EventMutIteratorWithId<'a, E> { - type Item = (&'a mut E, EventId); - fn next(&mut self) -> Option { - match self - .chain - .next() - .map(|instance| (&mut instance.event, instance.event_id)) - { - Some(item) => { - #[cfg(feature = "detailed_trace")] - tracing::trace!("EventMutator::iter() -> {}", item.1); - self.mutator.last_event_count += 1; - self.unread -= 1; - Some(item) - } - None => None, - } - } - - fn size_hint(&self) -> (usize, Option) { - self.chain.size_hint() - } - - fn count(self) -> usize { - self.mutator.last_event_count += self.unread; - self.unread - } - - fn last(self) -> Option - where - Self: Sized, - { - let EventInstance { event_id, event } = self.chain.last()?; - self.mutator.last_event_count += self.unread; - Some((event, *event_id)) - } - - fn nth(&mut self, n: usize) -> Option { - if let Some(EventInstance { event_id, event }) = self.chain.nth(n) { - self.mutator.last_event_count += n + 1; - self.unread -= n + 1; - Some((event, *event_id)) - } else { - self.mutator.last_event_count += self.unread; - self.unread = 0; - None - } - } -} - -impl<'a, E: BufferedEvent> ExactSizeIterator for EventMutIteratorWithId<'a, E> { - fn len(&self) -> usize { - self.unread - } -} - -/// A parallel iterator over `BufferedEvent`s. -#[derive(Debug)] -#[cfg(feature = "multi_threaded")] -pub struct EventMutParIter<'a, E: BufferedEvent> { - mutator: &'a mut EventCursor, - slices: [&'a mut [EventInstance]; 2], - batching_strategy: BatchingStrategy, - #[cfg(not(target_arch = "wasm32"))] - unread: usize, -} - -#[cfg(feature = "multi_threaded")] -impl<'a, E: BufferedEvent> EventMutParIter<'a, E> { - /// Creates a new parallel iterator over `events` that have not yet been seen by `mutator`. - pub fn new(mutator: &'a mut EventCursor, events: &'a mut Events) -> Self { - let a_index = mutator - .last_event_count - .saturating_sub(events.events_a.start_event_count); - let b_index = mutator - .last_event_count - .saturating_sub(events.events_b.start_event_count); - let a = events.events_a.get_mut(a_index..).unwrap_or_default(); - let b = events.events_b.get_mut(b_index..).unwrap_or_default(); - - let unread_count = a.len() + b.len(); - mutator.last_event_count = events.event_count - unread_count; - - Self { - mutator, - slices: [a, b], - batching_strategy: BatchingStrategy::default(), - #[cfg(not(target_arch = "wasm32"))] - unread: unread_count, - } - } - - /// Changes the batching strategy used when iterating. - /// - /// For more information on how this affects the resultant iteration, see - /// [`BatchingStrategy`]. - pub fn batching_strategy(mut self, strategy: BatchingStrategy) -> Self { - self.batching_strategy = strategy; - self - } - - /// Runs the provided closure for each unread event in parallel. - /// - /// Unlike normal iteration, the event order is not guaranteed in any form. - /// - /// # Panics - /// If the [`ComputeTaskPool`] is not initialized. If using this from an event reader that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - pub fn for_each(self, func: FN) { - self.for_each_with_id(move |e, _| func(e)); - } - - /// Runs the provided closure for each unread event in parallel, like [`for_each`](Self::for_each), - /// but additionally provides the `EventId` to the closure. - /// - /// Note that the order of iteration is not guaranteed, but `EventId`s are ordered by send order. - /// - /// # Panics - /// If the [`ComputeTaskPool`] is not initialized. If using this from an event reader that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[cfg_attr( - target_arch = "wasm32", - expect(unused_mut, reason = "not mutated on this target") - )] - pub fn for_each_with_id) + Send + Sync + Clone>( - mut self, - func: FN, - ) { - #[cfg(target_arch = "wasm32")] - { - self.into_iter().for_each(|(e, i)| func(e, i)); - } - - #[cfg(not(target_arch = "wasm32"))] - { - let pool = bevy_tasks::ComputeTaskPool::get(); - let thread_count = pool.thread_num(); - if thread_count <= 1 { - return self.into_iter().for_each(|(e, i)| func(e, i)); - } - - let batch_size = self - .batching_strategy - .calc_batch_size(|| self.len(), thread_count); - let chunks = self.slices.map(|s| s.chunks_mut(batch_size)); - - pool.scope(|scope| { - for batch in chunks.into_iter().flatten() { - let func = func.clone(); - scope.spawn(async move { - for event in batch { - func(&mut event.event, event.event_id); - } - }); - } - }); - - // Events are guaranteed to be read at this point. - self.mutator.last_event_count += self.unread; - self.unread = 0; - } - } - - /// Returns the number of [`BufferedEvent`]s to be iterated. - pub fn len(&self) -> usize { - self.slices.iter().map(|s| s.len()).sum() - } - - /// Returns [`true`] if there are no events remaining in this iterator. - pub fn is_empty(&self) -> bool { - self.slices.iter().all(|x| x.is_empty()) - } -} - -#[cfg(feature = "multi_threaded")] -impl<'a, E: BufferedEvent> IntoIterator for EventMutParIter<'a, E> { - type IntoIter = EventMutIteratorWithId<'a, E>; - type Item = ::Item; - - fn into_iter(self) -> Self::IntoIter { - let EventMutParIter { - mutator: reader, - slices: [a, b], - .. - } = self; - let unread = a.len() + b.len(); - let chain = a.iter_mut().chain(b); - EventMutIteratorWithId { - mutator: reader, - chain, - unread, - } - } -} diff --git a/src/event/mutator.rs b/src/event/mutator.rs deleted file mode 100644 index a9c9459..0000000 --- a/src/event/mutator.rs +++ /dev/null @@ -1,143 +0,0 @@ -#[cfg(feature = "multi_threaded")] -use bevy_ecs::event::EventMutParIter; -use bevy_ecs::{ - event::{BufferedEvent, EventCursor, EventMutIterator, EventMutIteratorWithId, Events}, - system::{Local, ResMut, SystemParam}, -}; - -/// Mutably reads events of type `T` keeping track of which events have already been read -/// by each system allowing multiple systems to read the same events. Ideal for chains of systems -/// that all want to modify the same events. -/// -/// # Usage -/// -/// `EventMutators`s are usually declared as a [`SystemParam`]. -/// ``` -/// # use bevy_ecs::prelude::*; -/// -/// #[derive(Event, BufferedEvent, Debug)] -/// pub struct MyEvent(pub u32); // Custom event type. -/// fn my_system(mut reader: EventMutator) { -/// for event in reader.read() { -/// event.0 += 1; -/// println!("received event: {:?}", event); -/// } -/// } -/// ``` -/// -/// # Concurrency -/// -/// Multiple systems with `EventMutator` of the same event type can not run concurrently. -/// They also can not be executed in parallel with [`EventReader`] or [`EventWriter`]. -/// -/// # Clearing, Reading, and Peeking -/// -/// Events are stored in a double buffered queue that switches each frame. This switch also clears the previous -/// frame's events. Events should be read each frame otherwise they may be lost. For manual control over this -/// behavior, see [`Events`]. -/// -/// Most of the time systems will want to use [`EventMutator::read()`]. This function creates an iterator over -/// all events that haven't been read yet by this system, marking the event as read in the process. -/// -/// [`EventReader`]: super::EventReader -/// [`EventWriter`]: super::EventWriter -#[derive(SystemParam, Debug)] -pub struct EventMutator<'w, 's, E: BufferedEvent> { - pub(super) reader: Local<'s, EventCursor>, - #[system_param(validation_message = "BufferedEvent not initialized")] - events: ResMut<'w, Events>, -} - -impl<'w, 's, E: BufferedEvent> EventMutator<'w, 's, E> { - /// Iterates over the events this [`EventMutator`] has not seen yet. This updates the - /// [`EventMutator`]'s event counter, which means subsequent event reads will not include events - /// that happened before now. - pub fn read(&mut self) -> EventMutIterator<'_, E> { - self.reader.read_mut(&mut self.events) - } - - /// Like [`read`](Self::read), except also returning the [`EventId`](super::EventId) of the events. - pub fn read_with_id(&mut self) -> EventMutIteratorWithId<'_, E> { - self.reader.read_mut_with_id(&mut self.events) - } - - /// Returns a parallel iterator over the events this [`EventMutator`] has not seen yet. - /// See also [`for_each`](super::EventParIter::for_each). - /// - /// # Example - /// ``` - /// # use bevy_ecs::prelude::*; - /// # use std::sync::atomic::{AtomicUsize, Ordering}; - /// - /// #[derive(Event, BufferedEvent)] - /// struct MyEvent { - /// value: usize, - /// } - /// - /// #[derive(Resource, Default)] - /// struct Counter(AtomicUsize); - /// - /// // setup - /// let mut world = World::new(); - /// world.init_resource::>(); - /// world.insert_resource(Counter::default()); - /// - /// let mut schedule = Schedule::default(); - /// schedule.add_systems(|mut events: EventMutator, counter: Res| { - /// events.par_read().for_each(|MyEvent { value }| { - /// counter.0.fetch_add(*value, Ordering::Relaxed); - /// }); - /// }); - /// for value in 0..100 { - /// world.send_event(MyEvent { value }); - /// } - /// schedule.run(&mut world); - /// let Counter(counter) = world.remove_resource::().unwrap(); - /// // all events were processed - /// assert_eq!(counter.into_inner(), 4950); - /// ``` - #[cfg(feature = "multi_threaded")] - pub fn par_read(&mut self) -> EventMutParIter<'_, E> { - self.reader.par_read_mut(&mut self.events) - } - - /// Determines the number of events available to be read from this [`EventMutator`] without consuming any. - pub fn len(&self) -> usize { - self.reader.len(&self.events) - } - - /// Returns `true` if there are no events available to read. - /// - /// # Example - /// - /// The following example shows a useful pattern where some behavior is triggered if new events are available. - /// [`EventMutator::clear()`] is used so the same events don't re-trigger the behavior the next time the system runs. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// #[derive(Event, BufferedEvent)] - /// struct CollisionEvent; - /// - /// fn play_collision_sound(mut events: EventMutator) { - /// if !events.is_empty() { - /// events.clear(); - /// // Play a sound - /// } - /// } - /// # bevy_ecs::system::assert_is_system(play_collision_sound); - /// ``` - pub fn is_empty(&self) -> bool { - self.reader.is_empty(&self.events) - } - - /// Consumes all available events. - /// - /// This means these events will not appear in calls to [`EventMutator::read()`] or - /// [`EventMutator::read_with_id()`] and [`EventMutator::is_empty()`] will return `true`. - /// - /// For usage, see [`EventMutator::is_empty()`]. - pub fn clear(&mut self) { - self.reader.clear(&self.events); - } -} diff --git a/src/event/reader.rs b/src/event/reader.rs deleted file mode 100644 index e15b3ea..0000000 --- a/src/event/reader.rs +++ /dev/null @@ -1,115 +0,0 @@ -#[cfg(feature = "multi_threaded")] -use bevy_ecs::event::EventParIter; -use bevy_ecs::{ - event::{BufferedEvent, EventCursor, EventIterator, EventIteratorWithId, Events}, - system::{Local, Res, SystemParam}, -}; - -/// Reads [`BufferedEvent`]s of type `T` in order and tracks which events have already been read. -/// -/// # Concurrency -/// -/// Unlike [`EventWriter`], systems with `EventReader` param can be executed concurrently -/// (but not concurrently with `EventWriter` or `EventMutator` systems for the same event type). -/// -/// [`EventWriter`]: super::EventWriter -#[derive(SystemParam, Debug)] -pub struct EventReader<'w, 's, E: BufferedEvent> { - pub(super) reader: Local<'s, EventCursor>, - #[system_param(validation_message = "BufferedEvent not initialized")] - events: Res<'w, Events>, -} - -impl<'w, 's, E: BufferedEvent> EventReader<'w, 's, E> { - /// Iterates over the events this [`EventReader`] has not seen yet. This updates the - /// [`EventReader`]'s event counter, which means subsequent event reads will not include events - /// that happened before now. - pub fn read(&mut self) -> EventIterator<'_, E> { - self.reader.read(&self.events) - } - - /// Like [`read`](Self::read), except also returning the [`EventId`](super::EventId) of the events. - pub fn read_with_id(&mut self) -> EventIteratorWithId<'_, E> { - self.reader.read_with_id(&self.events) - } - - /// Returns a parallel iterator over the events this [`EventReader`] has not seen yet. - /// See also [`for_each`](EventParIter::for_each). - /// - /// # Example - /// ``` - /// # use bevy_ecs::prelude::*; - /// # use std::sync::atomic::{AtomicUsize, Ordering}; - /// - /// #[derive(Event, BufferedEvent)] - /// struct MyEvent { - /// value: usize, - /// } - /// - /// #[derive(Resource, Default)] - /// struct Counter(AtomicUsize); - /// - /// // setup - /// let mut world = World::new(); - /// world.init_resource::>(); - /// world.insert_resource(Counter::default()); - /// - /// let mut schedule = Schedule::default(); - /// schedule.add_systems(|mut events: EventReader, counter: Res| { - /// events.par_read().for_each(|MyEvent { value }| { - /// counter.0.fetch_add(*value, Ordering::Relaxed); - /// }); - /// }); - /// for value in 0..100 { - /// world.send_event(MyEvent { value }); - /// } - /// schedule.run(&mut world); - /// let Counter(counter) = world.remove_resource::().unwrap(); - /// // all events were processed - /// assert_eq!(counter.into_inner(), 4950); - /// ``` - #[cfg(feature = "multi_threaded")] - pub fn par_read(&mut self) -> EventParIter<'_, E> { - self.reader.par_read(&self.events) - } - - /// Determines the number of events available to be read from this [`EventReader`] without consuming any. - pub fn len(&self) -> usize { - self.reader.len(&self.events) - } - - /// Returns `true` if there are no events available to read. - /// - /// # Example - /// - /// The following example shows a useful pattern where some behavior is triggered if new events are available. - /// [`EventReader::clear()`] is used so the same events don't re-trigger the behavior the next time the system runs. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// #[derive(Event, BufferedEvent)] - /// struct CollisionEvent; - /// - /// fn play_collision_sound(mut events: EventReader) { - /// if !events.is_empty() { - /// events.clear(); - /// // Play a sound - /// } - /// } - /// # bevy_ecs::system::assert_is_system(play_collision_sound); - /// ``` - pub fn is_empty(&self) -> bool { - self.reader.is_empty(&self.events) - } - - /// Consumes all available events. - /// - /// This means these events will not appear in calls to [`EventReader::read()`] or - /// [`EventReader::read_with_id()`] and [`EventReader::is_empty()`] will return `true`. - /// - /// For usage, see [`EventReader::is_empty()`]. - pub fn clear(&mut self) { - self.reader.clear(&self.events); - } -} diff --git a/src/event/registry.rs b/src/event/registry.rs deleted file mode 100644 index 7889de6..0000000 --- a/src/event/registry.rs +++ /dev/null @@ -1,93 +0,0 @@ -use alloc::vec::Vec; -use bevy_ecs::{ - change_detection::{DetectChangesMut, MutUntyped}, - component::{ComponentId, Tick}, - event::{BufferedEvent, Events}, - resource::Resource, - world::World, -}; - -#[doc(hidden)] -struct RegisteredEvent { - component_id: ComponentId, - // Required to flush the secondary buffer and drop events even if left unchanged. - previously_updated: bool, - // SAFETY: The component ID and the function must be used to fetch the Events resource - // of the same type initialized in `register_event`, or improper type casts will occur. - update: unsafe fn(MutUntyped), -} - -/// A registry of all of the [`Events`] in the [`World`], used by [`event_update_system`](crate::event::update::event_update_system) -/// to update all of the events. -#[derive(Resource, Default)] -pub struct EventRegistry { - /// Should the events be updated? - /// - /// This field is generally automatically updated by the [`signal_event_update_system`](crate::event::update::signal_event_update_system). - pub should_update: ShouldUpdateEvents, - event_updates: Vec, -} - -/// Controls whether or not the events in an [`EventRegistry`] should be updated. -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] -pub enum ShouldUpdateEvents { - /// Without any fixed timestep, events should always be updated each frame. - #[default] - Always, - /// We need to wait until at least one pass of the fixed update schedules to update the events. - Waiting, - /// At least one pass of the fixed update schedules has occurred, and the events are ready to be updated. - Ready, -} - -impl EventRegistry { - /// Registers an event type to be updated in a given [`World`] - /// - /// If no instance of the [`EventRegistry`] exists in the world, this will add one - otherwise it will use - /// the existing instance. - pub fn register_event(world: &mut World) { - // By initializing the resource here, we can be sure that it is present, - // and receive the correct, up-to-date `ComponentId` even if it was previously removed. - let component_id = world.init_resource::>(); - let mut registry = world.get_resource_or_init::(); - registry.event_updates.push(RegisteredEvent { - component_id, - previously_updated: false, - update: |ptr| { - // SAFETY: The resource was initialized with the type Events. - unsafe { ptr.with_type::>() } - .bypass_change_detection() - .update(); - }, - }); - } - - /// Updates all of the registered events in the World. - pub fn run_updates(&mut self, world: &mut World, last_change_tick: Tick) { - for registered_event in &mut self.event_updates { - // Bypass the type ID -> Component ID lookup with the cached component ID. - if let Some(events) = world.get_resource_mut_by_id(registered_event.component_id) { - let has_changed = events.has_changed_since(last_change_tick); - if registered_event.previously_updated || has_changed { - // SAFETY: The update function pointer is called with the resource - // fetched from the same component ID. - unsafe { (registered_event.update)(events) }; - // Always set to true if the events have changed, otherwise disable running on the second invocation - // to wait for more changes. - registered_event.previously_updated = - has_changed || !registered_event.previously_updated; - } - } - } - } - - /// Removes an event from the world and its associated [`EventRegistry`]. - pub fn deregister_events(world: &mut World) { - let component_id = world.init_resource::>(); - let mut registry = world.get_resource_or_init::(); - registry - .event_updates - .retain(|e| e.component_id != component_id); - world.remove_resource::>(); - } -} diff --git a/src/event/update.rs b/src/event/update.rs deleted file mode 100644 index bdde1af..0000000 --- a/src/event/update.rs +++ /dev/null @@ -1,65 +0,0 @@ -use bevy_ecs::{ - change_detection::Mut, - component::Tick, - event::EventRegistry, - system::{Local, Res, ResMut}, - world::World, -}; -use bevy_ecs_macros::SystemSet; -#[cfg(feature = "bevy_reflect")] -use core::hash::Hash; - -use super::registry::ShouldUpdateEvents; - -#[doc(hidden)] -#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] -pub struct EventUpdateSystems; - -/// Deprecated alias for [`EventUpdateSystems`]. -#[deprecated(since = "0.17.0", note = "Renamed to `EventUpdateSystems`.")] -pub type EventUpdates = EventUpdateSystems; - -/// Signals the [`event_update_system`] to run after `FixedUpdate` systems. -/// -/// This will change the behavior of the [`EventRegistry`] to only run after a fixed update cycle has passed. -/// Normally, this will simply run every frame. -pub fn signal_event_update_system(signal: Option>) { - if let Some(mut registry) = signal { - registry.should_update = ShouldUpdateEvents::Ready; - } -} - -/// A system that calls [`Events::update`](super::Events::update) on all registered [`Events`][super::Events] in the world. -pub fn event_update_system(world: &mut World, mut last_change_tick: Local) { - if world.contains_resource::() { - world.resource_scope(|world, mut registry: Mut| { - registry.run_updates(world, *last_change_tick); - - registry.should_update = match registry.should_update { - // If we're always updating, keep doing so. - ShouldUpdateEvents::Always => ShouldUpdateEvents::Always, - // Disable the system until signal_event_update_system runs again. - ShouldUpdateEvents::Waiting | ShouldUpdateEvents::Ready => { - ShouldUpdateEvents::Waiting - } - }; - }); - } - *last_change_tick = world.change_tick(); -} - -/// A run condition for [`event_update_system`]. -/// -/// If [`signal_event_update_system`] has been run at least once, -/// we will wait for it to be run again before updating the events. -/// -/// Otherwise, we will always update the events. -pub fn event_update_condition(maybe_signal: Option>) -> bool { - match maybe_signal { - Some(signal) => match signal.should_update { - ShouldUpdateEvents::Always | ShouldUpdateEvents::Ready => true, - ShouldUpdateEvents::Waiting => false, - }, - None => true, - } -} diff --git a/src/event/writer.rs b/src/event/writer.rs deleted file mode 100644 index 4c38401..0000000 --- a/src/event/writer.rs +++ /dev/null @@ -1,101 +0,0 @@ -use bevy_ecs::{ - event::{BufferedEvent, EventId, Events, SendBatchIds}, - system::{ResMut, SystemParam}, -}; - -/// Sends [`BufferedEvent`]s of type `T`. -/// -/// # Usage -/// -/// `EventWriter`s are usually declared as a [`SystemParam`]. -/// ``` -/// # use bevy_ecs::prelude::*; -/// -/// #[derive(Event, BufferedEvent)] -/// pub struct MyEvent; // Custom event type. -/// fn my_system(mut writer: EventWriter) { -/// writer.write(MyEvent); -/// } -/// -/// # bevy_ecs::system::assert_is_system(my_system); -/// ``` -/// # Observers -/// -/// "Buffered" events, such as those sent directly in [`Events`] or written using [`EventWriter`], do _not_ automatically -/// trigger any [`Observer`]s watching for that event, as each [`BufferedEvent`] has different requirements regarding _if_ it will -/// be triggered, and if so, _when_ it will be triggered in the schedule. -/// -/// # Concurrency -/// -/// `EventWriter` param has [`ResMut>`](Events) inside. So two systems declaring `EventWriter` params -/// for the same event type won't be executed concurrently. -/// -/// # Untyped events -/// -/// `EventWriter` can only write events of one specific type, which must be known at compile-time. -/// This is not a problem most of the time, but you may find a situation where you cannot know -/// ahead of time every kind of event you'll need to send. In this case, you can use the "type-erased event" pattern. -/// -/// ``` -/// # use bevy_ecs::{prelude::*, event::Events}; -/// # #[derive(Event, BufferedEvent)] -/// # pub struct MyEvent; -/// fn send_untyped(mut commands: Commands) { -/// // Send an event of a specific type without having to declare that -/// // type as a SystemParam. -/// // -/// // Effectively, we're just moving the type parameter from the /type/ to the /method/, -/// // which allows one to do all kinds of clever things with type erasure, such as sending -/// // custom events to unknown 3rd party plugins (modding API). -/// // -/// // NOTE: the event won't actually be sent until commands get applied during -/// // apply_deferred. -/// commands.queue(|w: &mut World| { -/// w.send_event(MyEvent); -/// }); -/// } -/// ``` -/// Note that this is considered *non-idiomatic*, and should only be used when `EventWriter` will not work. -/// -/// [`Observer`]: crate::observer::Observer -#[derive(SystemParam)] -pub struct EventWriter<'w, E: BufferedEvent> { - #[system_param(validation_message = "BufferedEvent not initialized")] - events: ResMut<'w, Events>, -} - -impl<'w, E: BufferedEvent> EventWriter<'w, E> { - /// Writes an `event`, which can later be read by [`EventReader`](super::EventReader)s. - /// This method returns the [ID](`EventId`) of the written `event`. - /// - /// See [`Events`] for details. - #[doc(alias = "send")] - #[track_caller] - pub fn write(&mut self, event: E) -> EventId { - self.events.send(event) - } - - /// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s. - /// This is more efficient than sending each event individually. - /// This method returns the [IDs](`EventId`) of the written `events`. - /// - /// See [`Events`] for details. - #[doc(alias = "send_batch")] - #[track_caller] - pub fn write_batch(&mut self, events: impl IntoIterator) -> SendBatchIds { - self.events.send_batch(events) - } - - /// Writes the default value of the event. Useful when the event is an empty struct. - /// This method returns the [ID](`EventId`) of the written `event`. - /// - /// See [`Events`] for details. - #[doc(alias = "send_default")] - #[track_caller] - pub fn write_default(&mut self) -> EventId - where - E: Default, - { - self.events.send_default() - } -} diff --git a/src/game_world.rs b/src/game_world.rs index 3d0e637..03380c5 100644 --- a/src/game_world.rs +++ b/src/game_world.rs @@ -5,29 +5,32 @@ //! Follows the same wrapper pattern as `HookedWorld`, `TrackedWorld`, `ObservedWorld`. //! //! # Example -//! ```ignore +//! ```no_run +//! use cougr_core::accounts::{ClassicAccount, GameAction}; +//! use cougr_core::game_world::GameWorld; +//! use cougr_core::simple_world::SimpleWorld; +//! use soroban_sdk::{symbol_short, testutils::Address as _, Address, Bytes, Env}; +//! +//! let env = Env::default(); //! let world = SimpleWorld::new(&env); -//! let account = ClassicAccount::new(player_address); -//! let mut game = GameWorld::new(world, account); +//! let account = ClassicAccount::new(Address::generate(&env)); +//! let game = GameWorld::new(world, account); //! -//! // Execute authorized actions +//! let action = GameAction { system_name: symbol_short!("move"), data: Bytes::new(&env) }; //! game.execute_authorized(&env, &action)?; -//! -//! // Submit and verify ZK proofs -//! game.submit_proof(&env, entity_id, &vk, &proof, &public_inputs)?; +//! # Ok::<(), cougr_core::accounts::AccountError>(()) //! ``` use soroban_sdk::Env; -use crate::accounts::degradation::authorize_with_fallback; -use crate::accounts::error::AccountError; -use crate::accounts::traits::{CougrAccount, SessionKeyProvider}; -use crate::accounts::types::{AccountCapabilities, GameAction, SessionKey, SessionScope}; +use crate::accounts::{ + AccountCapabilities, AccountError, AuthResult, CougrAccount, GameAction, IntentAccount, + SessionKey, SessionKeyProvider, SessionScope, SignedIntent, +}; use crate::component::ComponentTrait; use crate::simple_world::{EntityId, SimpleWorld}; -use crate::zk::error::ZKError; -use crate::zk::systems::verify_proofs_system; -use crate::zk::types::{Groth16Proof, Scalar, VerificationKey}; +use crate::zk::experimental::{verify_proofs_system, Groth16Proof, VerificationKey}; +use crate::zk::{Scalar, ZKError}; /// Game world that integrates ECS, ZK proofs, and account authorization. pub struct GameWorld { @@ -62,12 +65,12 @@ impl GameWorld { Ok(verified) } - /// Execute an authorized game action. + /// Execute an owner-authorized game action. /// - /// Uses the active session if available and valid, otherwise falls back - /// to direct authorization via the account. + /// This path is explicit direct authorization and does not infer or consume + /// sessions implicitly. pub fn execute_authorized(&self, env: &Env, action: &GameAction) -> Result<(), AccountError> { - authorize_with_fallback(env, &self.account, action, self.active_session.as_ref()) + self.account.authorize(env, action) } /// Set an active session key for this game world. @@ -152,6 +155,47 @@ impl GameWorld { } } +impl GameWorld { + /// Execute a signed intent through the account kernel. + pub fn execute_intent( + &mut self, + env: &Env, + intent: &SignedIntent, + ) -> Result { + self.account.authorize_intent(env, intent) + } + + /// Execute an action using the currently active session. + /// + /// This path is explicit session authorization. It does not fall back to + /// owner auth if the session is invalid or exhausted. + pub fn execute_with_active_session( + &mut self, + env: &Env, + action: &GameAction, + ) -> Result { + let session = self + .active_session + .as_ref() + .ok_or(AccountError::SessionRevoked)? + .clone(); + let intent = SignedIntent::session( + env, + self.account.address().clone(), + &session.key_id, + action.clone(), + session.next_nonce, + session.scope.expires_at, + ); + let result = self.account.authorize_intent(env, &intent)?; + if let Some(active) = self.active_session.as_mut() { + active.operations_used += 1; + active.next_nonce += 1; + } + Ok(result) + } +} + /// Extension methods for `GameWorld` when the account supports session keys. impl GameWorld { /// Start a new session for gasless gameplay. @@ -172,9 +216,8 @@ impl GameWorld { mod tests { use super::*; use crate::accounts::testing::MockAccount; - use crate::accounts::types::SessionScope; use crate::component::Position; - use crate::zk::types::{G1Point, G2Point}; + use crate::zk::{G1Point, G2Point}; use soroban_sdk::{symbol_short, vec, Bytes, BytesN, Env, Vec}; fn make_game_world(env: &Env) -> GameWorld { @@ -244,6 +287,7 @@ mod tests { }, created_at: 0, operations_used: 0, + next_nonce: 0, }; game.set_session(session); @@ -267,6 +311,7 @@ mod tests { }, created_at: 0, operations_used: 0, + next_nonce: 0, }; game.set_session(session); diff --git a/src/hooks.rs b/src/hooks.rs index 20689a4..7ca94a7 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -15,11 +15,15 @@ pub type OnRemoveHook = fn(entity_id: EntityId, component_type: &Symbol); /// such as updating indexes or cleaning up related state. /// /// # Example -/// ```ignore +/// ``` +/// use cougr_core::hooks::HookRegistry; +/// use soroban_sdk::{symbol_short, Bytes, Symbol}; +/// +/// fn on_add(_entity_id: u32, _ctype: &Symbol, _data: &Bytes) {} +/// /// let mut hooks = HookRegistry::new(); -/// hooks.on_add(symbol_short!("pos"), |entity_id, ctype, data| { -/// // React to position being added -/// }); +/// hooks.on_add(symbol_short!("pos"), on_add); +/// assert_eq!(hooks.add_hook_count(), 1); /// ``` pub struct HookRegistry { add_hooks: Vec<(Symbol, OnAddHook)>, @@ -86,12 +90,20 @@ impl Default for HookRegistry { /// this wrapper carries a separate `HookRegistry` alongside the world. /// /// # Example -/// ```ignore +/// ``` +/// use cougr_core::hooks::HookedWorld; +/// use cougr_core::simple_world::SimpleWorld; +/// use soroban_sdk::{symbol_short, Bytes, Env, Symbol}; +/// +/// fn on_add(_entity_id: u32, _ctype: &Symbol, _data: &Bytes) {} +/// /// let env = Env::default(); /// let world = SimpleWorld::new(&env); /// let mut hooked = HookedWorld::new(world); -/// hooked.hooks_mut().on_add(symbol_short!("pos"), |eid, ct, d| { /* ... */ }); -/// hooked.add_component(entity_id, symbol_short!("pos"), data); +/// let entity_id = hooked.spawn_entity(); +/// hooked.hooks_mut().on_add(symbol_short!("pos"), on_add); +/// hooked.add_component(entity_id, symbol_short!("pos"), Bytes::new(&env)); +/// assert!(hooked.has_component(entity_id, &symbol_short!("pos"))); /// ``` pub struct HookedWorld { world: SimpleWorld, diff --git a/src/incremental/storage_world.rs b/src/incremental/storage_world.rs index 215e05a..e232f74 100644 --- a/src/incremental/storage_world.rs +++ b/src/incremental/storage_world.rs @@ -7,11 +7,15 @@ //! //! # Usage //! -//! ```ignore +//! ```no_run +//! use cougr_core::incremental::StorageWorld; +//! use soroban_sdk::{symbol_short, Bytes, Env}; +//! +//! let env = Env::default(); //! let mut world = StorageWorld::load_metadata(&env); -//! world.load_entity(&env, entity_id); -//! world.add_component(&env, entity_id, sym, data); -//! world.flush(&env); // only writes changed entries +//! let entity_id = world.spawn_entity(&env); +//! world.add_component(&env, entity_id, symbol_short!("hp"), Bytes::new(&env)); +//! world.flush(&env); //! ``` //! //! # Trade-offs @@ -34,13 +38,13 @@ use soroban_sdk::{contracttype, Bytes, Env, Symbol}; #[derive(Clone, Debug)] pub struct WorldMetadata { /// Next entity ID to assign. - pub next_entity_id: EntityId, + pub next_entity_id: u32, /// Version counter for cache invalidation. pub version: u64, /// Total number of live entities. pub entity_count: u32, /// List of all live entity IDs. - pub entity_ids: soroban_sdk::Vec, + pub entity_ids: soroban_sdk::Vec, } /// Cached entity data loaded from persistent storage. diff --git a/src/lib.rs b/src/lib.rs index a9c0edd..6548af8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,39 @@ #![no_std] #![allow(unsafe_code)] +#![doc = r#" +Cougr is a monolithic-on-the-outside ECS framework for Soroban-compatible applications. -extern crate alloc; +The public API is intentionally split into: + +- root re-exports for the onboarding path +- `accounts` for account abstraction and session flows +- `zk::stable` for stable privacy primitives +- `zk::experimental` for fast-moving proof-verification APIs + +# Golden Path + +```rust +use cougr_core::{ComponentTrait, Position, SimpleWorld}; +use soroban_sdk::Env; + +let env = Env::default(); +let mut world = SimpleWorld::new(&env); +let entity = world.spawn_entity(); +world.set_typed(&env, entity, &Position::new(1, 2)); + +let pos: Position = world.get_typed(&env, entity).unwrap(); +assert_eq!(pos.x, 1); +``` -use soroban_sdk::{Symbol, Vec}; +# Stability + +- ECS runtime and storage: Beta +- Accounts: Beta +- `zk::stable`: Stable subset +- `zk::experimental`: Experimental +"#] + +extern crate alloc; // Global allocator for WASM #[global_allocator] @@ -13,16 +43,15 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #[macro_use] pub mod macros; -// Core ECS types adapted for Soroban +// Public product domains pub mod accounts; pub mod archetype_world; pub mod change_tracker; pub mod commands; pub mod component; -pub mod components; #[cfg(feature = "debug")] +#[doc(hidden)] pub mod debug; -pub mod entity; pub mod error; pub mod event; pub mod game_world; @@ -34,18 +63,20 @@ pub mod query; pub mod resource; pub mod scheduler; pub mod simple_world; -pub mod storage; +pub mod standards; pub mod system; -pub mod systems; pub mod world; pub mod zk; -// Re-export core types +// Internal implementation modules kept out of the default public surface. +mod entity; +mod storage; + +// Root-level golden path re-exports. pub use archetype_world::{ArchetypeQueryCache, ArchetypeWorld}; pub use change_tracker::{ChangeTracker, TrackedWorld}; pub use commands::CommandQueue; -pub use component::{Component, ComponentId, ComponentStorage, ComponentTrait}; -pub use components::Position; +pub use component::{Component, ComponentId, ComponentStorage, ComponentTrait, Position}; pub use entity::{Entity, EntityId}; pub use error::{CougrError, CougrResult}; pub use event::{Event, EventReader, EventWriter}; @@ -59,58 +90,25 @@ pub use resource::Resource; pub use scheduler::{SimpleScheduler, SystemScheduler}; pub use simple_world::SimpleWorld; pub use storage::{SparseStorage, Storage, TableStorage}; -pub use system::{IntoSystem, System, SystemParam}; -pub use systems::MovementSystem; +pub use system::{IntoSystem, MovementSystem, System, SystemParam}; pub use world::World; -// Library functions for ECS operations -pub fn create_world() -> World { - World::new() -} - -pub fn spawn_entity(world: &mut World, components: Vec) -> EntityId { - let entity = world.spawn(components); - entity.id() -} - -pub fn add_component(world: &mut World, entity_id: EntityId, component: Component) -> bool { - world.add_component_to_entity(entity_id, component); - true -} - -pub fn remove_component(world: &mut World, entity_id: EntityId, component_type: Symbol) -> bool { - world.remove_component_from_entity(entity_id, &component_type) -} - -pub fn get_component( - world: &World, - entity_id: EntityId, - component_type: Symbol, -) -> Option { - world.get_component(entity_id, &component_type) -} - -pub fn query_entities( - _world: &World, - _component_types: Vec, - env: &soroban_sdk::Env, -) -> Vec { - // Since we can't easily convert Vec to &[Symbol] in Soroban, - // we'll need to restructure this. For now, return empty result. - Vec::new(env) +/// Common ECS imports for the default onboarding path. +pub mod prelude { + pub use super::{ + ArchetypeWorld, CommandQueue, Component, ComponentStorage, ComponentTrait, EntityId, + Position, Query, Resource, SimpleWorld, World, + }; } -// Predule for common types -pub mod prelude { +/// Advanced runtime primitives that remain supported but are not part of the +/// smallest onboarding surface. +pub mod runtime { pub use super::{ - component::{Component, ComponentId, ComponentStorage}, - entity::{Entity, EntityId}, - event::{Event, EventReader, EventWriter}, - query::{Query, QueryState}, - resource::Resource, - storage::{SparseStorage, Storage, TableStorage}, - system::{IntoSystem, System, SystemParam}, - world::World, + resource::Resource, ChangeTracker, Event, EventReader, EventWriter, HookRegistry, + HookedWorld, ObservedWorld, ObserverRegistry, Plugin, PluginApp, QueryState, + SimpleQueryCache, SimpleScheduler, StorageWorld, System, SystemParam, SystemScheduler, + TrackedWorld, }; } diff --git a/src/lifecycle.rs b/src/lifecycle.rs deleted file mode 100644 index e92c6cc..0000000 --- a/src/lifecycle.rs +++ /dev/null @@ -1,643 +0,0 @@ -//! This module contains various tools to allow you to react to component insertion or removal, -//! as well as entity spawning and despawning. -//! -//! There are four main ways to react to these lifecycle events: -//! -//! 1. Using component hooks, which act as inherent constructors and destructors for components. -//! 2. Using [observers], which are a user-extensible way to respond to events, including component lifecycle events. -//! 3. Using the [`RemovedComponents`] system parameter, which offers an event-style interface. -//! 4. Using the [`Added`] query filter, which checks each component to see if it has been added since the last time a system ran. -//! -//! [observers]: crate::observer -//! [`Added`]: crate::query::Added -//! -//! # Types of lifecycle events -//! -//! There are five types of lifecycle events, split into two categories. First, we have lifecycle events that are triggered -//! when a component is added to an entity: -//! -//! - [`Add`]: Triggered when a component is added to an entity that did not already have it. -//! - [`Insert`]: Triggered when a component is added to an entity, regardless of whether it already had it. -//! -//! When both events occur, [`Add`] hooks are evaluated before [`Insert`]. -//! -//! Next, we have lifecycle events that are triggered when a component is removed from an entity: -//! -//! - [`Replace`]: Triggered when a component is removed from an entity, regardless if it is then replaced with a new value. -//! - [`Remove`]: Triggered when a component is removed from an entity and not replaced, before the component is removed. -//! - [`Despawn`]: Triggered for each component on an entity when it is despawned. -//! -//! [`Replace`] hooks are evaluated before [`Remove`], then finally [`Despawn`] hooks are evaluated. -//! -//! [`Add`] and [`Remove`] are counterparts: they are only triggered when a component is added or removed -//! from an entity in such a way as to cause a change in the component's presence on that entity. -//! Similarly, [`Insert`] and [`Replace`] are counterparts: they are triggered when a component is added or replaced -//! on an entity, regardless of whether this results in a change in the component's presence on that entity. -//! -//! To reliably synchronize data structures using with component lifecycle events, -//! you can combine [`Insert`] and [`Replace`] to fully capture any changes to the data. -//! This is particularly useful in combination with immutable components, -//! to avoid any lifecycle-bypassing mutations. -//! -//! ## Lifecycle events and component types -//! -//! Despite the absence of generics, each lifecycle event is associated with a specific component. -//! When defining a component hook for a [`Component`] type, that component is used. -//! When listening to lifecycle events for observers, the `B: Bundle` generic is used. -//! -//! Each of these lifecycle events also corresponds to a fixed [`ComponentId`], -//! which are assigned during [`World`] initialization. -//! For example, [`Add`] corresponds to [`ADD`]. -//! This is used to skip [`TypeId`](core::any::TypeId) lookups in hot paths. -use crate::{ - change_detection::MaybeLocation, - component::{Component, ComponentId, ComponentIdFor, Tick}, - entity::Entity, - event::{ - BufferedEvent, EntityEvent, Event, EventCursor, EventId, EventIterator, - EventIteratorWithId, Events, - }, - query::FilteredAccessSet, - relationship::RelationshipHookMode, - storage::SparseSet, - system::{Local, ReadOnlySystemParam, SystemMeta, SystemParam}, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, -}; - -use derive_more::derive::Into; - -#[cfg(feature = "bevy_reflect")] -use bevy_reflect::Reflect; -use core::{ - fmt::Debug, - iter, - marker::PhantomData, - ops::{Deref, DerefMut}, - option, -}; - -/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`. -pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, HookContext); - -/// Context provided to a [`ComponentHook`]. -#[derive(Clone, Copy, Debug)] -pub struct HookContext { - /// The [`Entity`] this hook was invoked for. - pub entity: Entity, - /// The [`ComponentId`] this hook was invoked for. - pub component_id: ComponentId, - /// The caller location is `Some` if the `track_caller` feature is enabled. - pub caller: MaybeLocation, - /// Configures how relationship hooks will run - pub relationship_hook_mode: RelationshipHookMode, -} - -/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`]. -/// -/// Hooks are functions that run when a component is added, overwritten, or removed from an entity. -/// These are intended to be used for structural side effects that need to happen when a component is added or removed, -/// and are not intended for general-purpose logic. -/// -/// For example, you might use a hook to update a cached index when a component is added, -/// to clean up resources when a component is removed, -/// or to keep hierarchical data structures across entities in sync. -/// -/// This information is stored in the [`ComponentInfo`](crate::component::ComponentInfo) of the associated component. -/// -/// There are two ways of configuring hooks for a component: -/// 1. Defining the relevant hooks on the [`Component`] implementation -/// 2. Using the [`World::register_component_hooks`] method -/// -/// # Example -/// -/// ``` -/// use bevy_ecs::prelude::*; -/// use bevy_platform::collections::HashSet; -/// -/// #[derive(Component)] -/// struct MyTrackedComponent; -/// -/// #[derive(Resource, Default)] -/// struct TrackedEntities(HashSet); -/// -/// let mut world = World::new(); -/// world.init_resource::(); -/// -/// // No entities with `MyTrackedComponent` have been added yet, so we can safely add component hooks -/// let mut tracked_component_query = world.query::<&MyTrackedComponent>(); -/// assert!(tracked_component_query.iter(&world).next().is_none()); -/// -/// world.register_component_hooks::().on_add(|mut world, context| { -/// let mut tracked_entities = world.resource_mut::(); -/// tracked_entities.0.insert(context.entity); -/// }); -/// -/// world.register_component_hooks::().on_remove(|mut world, context| { -/// let mut tracked_entities = world.resource_mut::(); -/// tracked_entities.0.remove(&context.entity); -/// }); -/// -/// let entity = world.spawn(MyTrackedComponent).id(); -/// let tracked_entities = world.resource::(); -/// assert!(tracked_entities.0.contains(&entity)); -/// -/// world.despawn(entity); -/// let tracked_entities = world.resource::(); -/// assert!(!tracked_entities.0.contains(&entity)); -/// ``` -#[derive(Debug, Clone, Default)] -pub struct ComponentHooks { - pub(crate) on_add: Option, - pub(crate) on_insert: Option, - pub(crate) on_replace: Option, - pub(crate) on_remove: Option, - pub(crate) on_despawn: Option, -} - -impl ComponentHooks { - pub(crate) fn update_from_component(&mut self) -> &mut Self { - if let Some(hook) = C::on_add() { - self.on_add(hook); - } - if let Some(hook) = C::on_insert() { - self.on_insert(hook); - } - if let Some(hook) = C::on_replace() { - self.on_replace(hook); - } - if let Some(hook) = C::on_remove() { - self.on_remove(hook); - } - if let Some(hook) = C::on_despawn() { - self.on_despawn(hook); - } - - self - } - - /// Register a [`ComponentHook`] that will be run when this component is added to an entity. - /// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as - /// adding all of its components. - /// - /// # Panics - /// - /// Will panic if the component already has an `on_add` hook - pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_add(hook) - .expect("Component already has an on_add hook") - } - - /// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`) - /// or replaced. - /// - /// An `on_insert` hook always runs after any `on_add` hooks (if the entity didn't already have the component). - /// - /// # Warning - /// - /// The hook won't run if the component is already present and is only mutated, such as in a system via a query. - /// As a result, this needs to be combined with immutable components to serve as a mechanism for reliably updating indexes and other caches. - /// - /// # Panics - /// - /// Will panic if the component already has an `on_insert` hook - pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_insert(hook) - .expect("Component already has an on_insert hook") - } - - /// Register a [`ComponentHook`] that will be run when this component is about to be dropped, - /// such as being replaced (with `.insert`) or removed. - /// - /// If this component is inserted onto an entity that already has it, this hook will run before the value is replaced, - /// allowing access to the previous data just before it is dropped. - /// This hook does *not* run if the entity did not already have this component. - /// - /// An `on_replace` hook always runs before any `on_remove` hooks (if the component is being removed from the entity). - /// - /// # Warning - /// - /// The hook won't run if the component is already present and is only mutated, such as in a system via a query. - /// As a result, this needs to be combined with immutable components to serve as a mechanism for reliably updating indexes and other caches. - /// - /// # Panics - /// - /// Will panic if the component already has an `on_replace` hook - pub fn on_replace(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_replace(hook) - .expect("Component already has an on_replace hook") - } - - /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. - /// Despawning an entity counts as removing all of its components. - /// - /// # Panics - /// - /// Will panic if the component already has an `on_remove` hook - pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_remove(hook) - .expect("Component already has an on_remove hook") - } - - /// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned. - /// - /// # Panics - /// - /// Will panic if the component already has an `on_despawn` hook - pub fn on_despawn(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_despawn(hook) - .expect("Component already has an on_despawn hook") - } - - /// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity. - /// - /// This is a fallible version of [`Self::on_add`]. - /// - /// Returns `None` if the component already has an `on_add` hook. - pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.on_add.is_some() { - return None; - } - self.on_add = Some(hook); - Some(self) - } - - /// Attempt to register a [`ComponentHook`] that will be run when this component is added (with `.insert`) - /// - /// This is a fallible version of [`Self::on_insert`]. - /// - /// Returns `None` if the component already has an `on_insert` hook. - pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.on_insert.is_some() { - return None; - } - self.on_insert = Some(hook); - Some(self) - } - - /// Attempt to register a [`ComponentHook`] that will be run when this component is replaced (with `.insert`) or removed - /// - /// This is a fallible version of [`Self::on_replace`]. - /// - /// Returns `None` if the component already has an `on_replace` hook. - pub fn try_on_replace(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.on_replace.is_some() { - return None; - } - self.on_replace = Some(hook); - Some(self) - } - - /// Attempt to register a [`ComponentHook`] that will be run when this component is removed from an entity. - /// - /// This is a fallible version of [`Self::on_remove`]. - /// - /// Returns `None` if the component already has an `on_remove` hook. - pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.on_remove.is_some() { - return None; - } - self.on_remove = Some(hook); - Some(self) - } - - /// Attempt to register a [`ComponentHook`] that will be run for each component on an entity when it is despawned. - /// - /// This is a fallible version of [`Self::on_despawn`]. - /// - /// Returns `None` if the component already has an `on_despawn` hook. - pub fn try_on_despawn(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.on_despawn.is_some() { - return None; - } - self.on_despawn = Some(hook); - Some(self) - } -} - -/// [`ComponentId`] for [`Add`] -pub const ADD: ComponentId = ComponentId::new(0); -/// [`ComponentId`] for [`Insert`] -pub const INSERT: ComponentId = ComponentId::new(1); -/// [`ComponentId`] for [`Replace`] -pub const REPLACE: ComponentId = ComponentId::new(2); -/// [`ComponentId`] for [`Remove`] -pub const REMOVE: ComponentId = ComponentId::new(3); -/// [`ComponentId`] for [`Despawn`] -pub const DESPAWN: ComponentId = ComponentId::new(4); - -/// Trigger emitted when a component is inserted onto an entity that does not already have that -/// component. Runs before `Insert`. -/// See [`crate::lifecycle::ComponentHooks::on_add`] for more information. -#[derive(Event, EntityEvent, Debug, Clone)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] -#[doc(alias = "OnAdd")] -pub struct Add; - -/// Trigger emitted when a component is inserted, regardless of whether or not the entity already -/// had that component. Runs after `Add`, if it ran. -/// See [`crate::lifecycle::ComponentHooks::on_insert`] for more information. -#[derive(Event, EntityEvent, Debug, Clone)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] -#[doc(alias = "OnInsert")] -pub struct Insert; - -/// Trigger emitted when a component is removed from an entity, regardless -/// of whether or not it is later replaced. -/// -/// Runs before the value is replaced, so you can still access the original component data. -/// See [`crate::lifecycle::ComponentHooks::on_replace`] for more information. -#[derive(Event, EntityEvent, Debug, Clone)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] -#[doc(alias = "OnReplace")] -pub struct Replace; - -/// Trigger emitted when a component is removed from an entity, and runs before the component is -/// removed, so you can still access the component data. -/// See [`crate::lifecycle::ComponentHooks::on_remove`] for more information. -#[derive(Event, EntityEvent, Debug, Clone)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] -#[doc(alias = "OnRemove")] -pub struct Remove; - -/// Trigger emitted for each component on an entity when it is despawned. -/// See [`crate::lifecycle::ComponentHooks::on_despawn`] for more information. -#[derive(Event, EntityEvent, Debug, Clone)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] -#[doc(alias = "OnDespawn")] -pub struct Despawn; - -/// Deprecated in favor of [`Add`]. -#[deprecated(since = "0.17.0", note = "Renamed to `Add`.")] -pub type OnAdd = Add; - -/// Deprecated in favor of [`Insert`]. -#[deprecated(since = "0.17.0", note = "Renamed to `Insert`.")] -pub type OnInsert = Insert; - -/// Deprecated in favor of [`Replace`]. -#[deprecated(since = "0.17.0", note = "Renamed to `Replace`.")] -pub type OnReplace = Replace; - -/// Deprecated in favor of [`Remove`]. -#[deprecated(since = "0.17.0", note = "Renamed to `Remove`.")] -pub type OnRemove = Remove; - -/// Deprecated in favor of [`Despawn`]. -#[deprecated(since = "0.17.0", note = "Renamed to `Despawn`.")] -pub type OnDespawn = Despawn; - -/// Wrapper around [`Entity`] for [`RemovedComponents`]. -/// Internally, `RemovedComponents` uses these as an `Events`. -#[derive(Event, BufferedEvent, Debug, Clone, Into)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Clone))] -pub struct RemovedComponentEntity(Entity); - -/// Wrapper around a [`EventCursor`] so that we -/// can differentiate events between components. -#[derive(Debug)] -pub struct RemovedComponentReader -where - T: Component, -{ - reader: EventCursor, - marker: PhantomData, -} - -impl Default for RemovedComponentReader { - fn default() -> Self { - Self { - reader: Default::default(), - marker: PhantomData, - } - } -} - -impl Deref for RemovedComponentReader { - type Target = EventCursor; - fn deref(&self) -> &Self::Target { - &self.reader - } -} - -impl DerefMut for RemovedComponentReader { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.reader - } -} - -/// Stores the [`RemovedComponents`] event buffers for all types of component in a given [`World`]. -#[derive(Default, Debug)] -pub struct RemovedComponentEvents { - event_sets: SparseSet>, -} - -impl RemovedComponentEvents { - /// Creates an empty storage buffer for component removal events. - pub fn new() -> Self { - Self::default() - } - - /// For each type of component, swaps the event buffers and clears the oldest event buffer. - /// In general, this should be called once per frame/update. - pub fn update(&mut self) { - for (_component_id, events) in self.event_sets.iter_mut() { - events.update(); - } - } - - /// Returns an iterator over components and their entity events. - pub fn iter(&self) -> impl Iterator)> { - self.event_sets.iter() - } - - /// Gets the event storage for a given component. - pub fn get( - &self, - component_id: impl Into, - ) -> Option<&Events> { - self.event_sets.get(component_id.into()) - } - - /// Sends a removal event for the specified component. - pub fn send(&mut self, component_id: impl Into, entity: Entity) { - self.event_sets - .get_or_insert_with(component_id.into(), Default::default) - .send(RemovedComponentEntity(entity)); - } -} - -/// A [`SystemParam`] that yields entities that had their `T` [`Component`] -/// removed or have been despawned with it. -/// -/// This acts effectively the same as an [`EventReader`](crate::event::EventReader). -/// -/// Unlike hooks or observers (see the [lifecycle](crate) module docs), -/// this does not allow you to see which data existed before removal. -/// -/// If you are using `bevy_ecs` as a standalone crate, -/// note that the [`RemovedComponents`] list will not be automatically cleared for you, -/// and will need to be manually flushed using [`World::clear_trackers`](World::clear_trackers). -/// -/// For users of `bevy` and `bevy_app`, [`World::clear_trackers`](World::clear_trackers) is -/// automatically called by `bevy_app::App::update` and `bevy_app::SubApp::update`. -/// For the main world, this is delayed until after all `SubApp`s have run. -/// -/// # Examples -/// -/// Basic usage: -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::lifecycle::RemovedComponents; -/// # -/// # #[derive(Component)] -/// # struct MyComponent; -/// fn react_on_removal(mut removed: RemovedComponents) { -/// removed.read().for_each(|removed_entity| println!("{}", removed_entity)); -/// } -/// # bevy_ecs::system::assert_is_system(react_on_removal); -/// ``` -#[derive(SystemParam)] -pub struct RemovedComponents<'w, 's, T: Component> { - component_id: ComponentIdFor<'s, T>, - reader: Local<'s, RemovedComponentReader>, - event_sets: &'w RemovedComponentEvents, -} - -/// Iterator over entities that had a specific component removed. -/// -/// See [`RemovedComponents`]. -pub type RemovedIter<'a> = iter::Map< - iter::Flatten>>>, - fn(RemovedComponentEntity) -> Entity, ->; - -/// Iterator over entities that had a specific component removed. -/// -/// See [`RemovedComponents`]. -pub type RemovedIterWithId<'a> = iter::Map< - iter::Flatten>>, - fn( - (&RemovedComponentEntity, EventId), - ) -> (Entity, EventId), ->; - -fn map_id_events( - (entity, id): (&RemovedComponentEntity, EventId), -) -> (Entity, EventId) { - (entity.clone().into(), id) -} - -// For all practical purposes, the api surface of `RemovedComponents` -// should be similar to `EventReader` to reduce confusion. -impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> { - /// Fetch underlying [`EventCursor`]. - pub fn reader(&self) -> &EventCursor { - &self.reader - } - - /// Fetch underlying [`EventCursor`] mutably. - pub fn reader_mut(&mut self) -> &mut EventCursor { - &mut self.reader - } - - /// Fetch underlying [`Events`]. - pub fn events(&self) -> Option<&Events> { - self.event_sets.get(self.component_id.get()) - } - - /// Destructures to get a mutable reference to the `EventCursor` - /// and a reference to `Events`. - /// - /// This is necessary since Rust can't detect destructuring through methods and most - /// usecases of the reader uses the `Events` as well. - pub fn reader_mut_with_events( - &mut self, - ) -> Option<( - &mut RemovedComponentReader, - &Events, - )> { - self.event_sets - .get(self.component_id.get()) - .map(|events| (&mut *self.reader, events)) - } - - /// Iterates over the events this [`RemovedComponents`] has not seen yet. This updates the - /// [`RemovedComponents`]'s event counter, which means subsequent event reads will not include events - /// that happened before now. - pub fn read(&mut self) -> RemovedIter<'_> { - self.reader_mut_with_events() - .map(|(reader, events)| reader.read(events).cloned()) - .into_iter() - .flatten() - .map(RemovedComponentEntity::into) - } - - /// Like [`read`](Self::read), except also returning the [`EventId`] of the events. - pub fn read_with_id(&mut self) -> RemovedIterWithId<'_> { - self.reader_mut_with_events() - .map(|(reader, events)| reader.read_with_id(events)) - .into_iter() - .flatten() - .map(map_id_events) - } - - /// Determines the number of removal events available to be read from this [`RemovedComponents`] without consuming any. - pub fn len(&self) -> usize { - self.events() - .map(|events| self.reader.len(events)) - .unwrap_or(0) - } - - /// Returns `true` if there are no events available to read. - pub fn is_empty(&self) -> bool { - self.events() - .is_none_or(|events| self.reader.is_empty(events)) - } - - /// Consumes all available events. - /// - /// This means these events will not appear in calls to [`RemovedComponents::read()`] or - /// [`RemovedComponents::read_with_id()`] and [`RemovedComponents::is_empty()`] will return `true`. - pub fn clear(&mut self) { - if let Some((reader, events)) = self.reader_mut_with_events() { - reader.clear(events); - } - } -} - -// SAFETY: Only reads World removed component events -unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {} - -// SAFETY: no component value access. -unsafe impl<'a> SystemParam for &'a RemovedComponentEvents { - type State = (); - type Item<'w, 's> = &'w RemovedComponentEvents; - - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - } - - #[inline] - unsafe fn get_param<'w, 's>( - _state: &'s mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - _change_tick: Tick, - ) -> Self::Item<'w, 's> { - world.removed_components() - } -} diff --git a/src/macros.rs b/src/macros.rs index 5ad31b9..92b5f9d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -189,10 +189,16 @@ macro_rules! __cougr_deserialize_field { /// The symbol name must be at most 9 characters (Soroban `symbol_short!` limit). /// /// # Example -/// ```ignore +/// ```no_run +/// use cougr_core::impl_component; +/// use soroban_sdk::contracttype; +/// /// #[contracttype] /// #[derive(Clone, Debug)] -/// pub struct Position { pub x: i32, pub y: i32 } +/// pub struct Position { +/// pub x: i32, +/// pub y: i32, +/// } /// /// impl_component!(Position, "position", Table, { x: i32, y: i32 }); /// ``` @@ -237,7 +243,9 @@ macro_rules! impl_component { /// Marker components have no data and serialize to a single byte `[1]`. /// /// # Example -/// ```ignore +/// ```no_run +/// use cougr_core::impl_marker_component; +/// /// pub struct SnakeHead; /// /// impl_marker_component!(SnakeHead, "snkhead", Sparse); @@ -273,12 +281,19 @@ macro_rules! impl_marker_component { /// Generates serialization/deserialization using big-endian byte encoding. /// /// # Example -/// ```ignore +/// ```no_run +/// use cougr_core::impl_resource; +/// use soroban_sdk::contracttype; +/// /// #[contracttype] /// #[derive(Clone)] -/// pub struct GameState { pub score: i32, pub level: i32, pub is_game_over: bool } +/// pub struct GameState { +/// pub score: i32, +/// pub level: i32, +/// pub is_game_over: bool, +/// } /// -/// impl_resource!(GameState, "gamestate", { score: i32, level: i32, is_game_over: bool }); +/// impl_resource!(GameState, "gamestat", { score: i32, level: i32, is_game_over: bool }); /// ``` #[macro_export] macro_rules! impl_resource { diff --git a/src/observer/centralized_storage.rs b/src/observer/centralized_storage.rs deleted file mode 100644 index e3fa6c5..0000000 --- a/src/observer/centralized_storage.rs +++ /dev/null @@ -1,245 +0,0 @@ -//! Centralized storage for observers, allowing for efficient look-ups. -//! -//! This has multiple levels: -//! - [`World::observers`] provides access to [`Observers`], which is a central storage for all observers. -//! - [`Observers`] contains multiple distinct caches in the form of [`CachedObservers`]. -//! - Most observers are looked up by the [`ComponentId`] of the event they are observing -//! - Lifecycle observers have their own fields to save lookups. -//! - [`CachedObservers`] contains maps of [`ObserverRunner`]s, which are the actual functions that will be run when the observer is triggered. -//! - These are split by target type, in order to allow for different lookup strategies. -//! - [`CachedComponentObservers`] is one of these maps, which contains observers that are specifically targeted at a component. - -use bevy_platform::collections::HashMap; - -use crate::{ - archetype::ArchetypeFlags, - change_detection::MaybeLocation, - component::ComponentId, - entity::EntityHashMap, - observer::{ObserverRunner, ObserverTrigger}, - prelude::*, - world::DeferredWorld, -}; - -/// An internal lookup table tracking all of the observers in the world. -/// -/// Stores a cache mapping trigger ids to the registered observers. -/// Some observer kinds (like [lifecycle](crate::lifecycle) observers) have a dedicated field, -/// saving lookups for the most common triggers. -/// -/// This can be accessed via [`World::observers`]. -#[derive(Default, Debug)] -pub struct Observers { - // Cached ECS observers to save a lookup most common triggers. - add: CachedObservers, - insert: CachedObservers, - replace: CachedObservers, - remove: CachedObservers, - despawn: CachedObservers, - // Map from trigger type to set of observers listening to that trigger - cache: HashMap, -} - -impl Observers { - pub(crate) fn get_observers_mut(&mut self, event_type: ComponentId) -> &mut CachedObservers { - use crate::lifecycle::*; - - match event_type { - ADD => &mut self.add, - INSERT => &mut self.insert, - REPLACE => &mut self.replace, - REMOVE => &mut self.remove, - DESPAWN => &mut self.despawn, - _ => self.cache.entry(event_type).or_default(), - } - } - - /// Attempts to get the observers for the given `event_type`. - /// - /// When accessing the observers for lifecycle events, such as [`Add`], [`Insert`], [`Replace`], [`Remove`], and [`Despawn`], - /// use the [`ComponentId`] constants from the [`lifecycle`](crate::lifecycle) module. - pub fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> { - use crate::lifecycle::*; - - match event_type { - ADD => Some(&self.add), - INSERT => Some(&self.insert), - REPLACE => Some(&self.replace), - REMOVE => Some(&self.remove), - DESPAWN => Some(&self.despawn), - _ => self.cache.get(&event_type), - } - } - - /// This will run the observers of the given `event_type`, targeting the given `entity` and `components`. - pub(crate) fn invoke( - mut world: DeferredWorld, - event_type: ComponentId, - current_target: Option, - original_target: Option, - components: impl Iterator + Clone, - data: &mut T, - propagate: &mut bool, - caller: MaybeLocation, - ) { - // SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld` - let (mut world, observers) = unsafe { - let world = world.as_unsafe_world_cell(); - // SAFETY: There are no outstanding world references - world.increment_trigger_id(); - let observers = world.observers(); - let Some(observers) = observers.try_get_observers(event_type) else { - return; - }; - // SAFETY: The only outstanding reference to world is `observers` - (world.into_deferred(), observers) - }; - - let trigger_for_components = components.clone(); - - let mut trigger_observer = |(&observer, runner): (&Entity, &ObserverRunner)| { - (runner)( - world.reborrow(), - ObserverTrigger { - observer, - event_type, - components: components.clone().collect(), - current_target, - original_target, - caller, - }, - data.into(), - propagate, - ); - }; - // Trigger observers listening for any kind of this trigger - observers - .global_observers - .iter() - .for_each(&mut trigger_observer); - - // Trigger entity observers listening for this kind of trigger - if let Some(target_entity) = current_target { - if let Some(map) = observers.entity_observers.get(&target_entity) { - map.iter().for_each(&mut trigger_observer); - } - } - - // Trigger observers listening to this trigger targeting a specific component - trigger_for_components.for_each(|id| { - if let Some(component_observers) = observers.component_observers.get(&id) { - component_observers - .global_observers - .iter() - .for_each(&mut trigger_observer); - - if let Some(target_entity) = current_target { - if let Some(map) = component_observers - .entity_component_observers - .get(&target_entity) - { - map.iter().for_each(&mut trigger_observer); - } - } - } - }); - } - - pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option { - use crate::lifecycle::*; - - match event_type { - ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER), - INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER), - REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER), - REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER), - DESPAWN => Some(ArchetypeFlags::ON_DESPAWN_OBSERVER), - _ => None, - } - } - - pub(crate) fn update_archetype_flags( - &self, - component_id: ComponentId, - flags: &mut ArchetypeFlags, - ) { - if self.add.component_observers.contains_key(&component_id) { - flags.insert(ArchetypeFlags::ON_ADD_OBSERVER); - } - - if self.insert.component_observers.contains_key(&component_id) { - flags.insert(ArchetypeFlags::ON_INSERT_OBSERVER); - } - - if self.replace.component_observers.contains_key(&component_id) { - flags.insert(ArchetypeFlags::ON_REPLACE_OBSERVER); - } - - if self.remove.component_observers.contains_key(&component_id) { - flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER); - } - - if self.despawn.component_observers.contains_key(&component_id) { - flags.insert(ArchetypeFlags::ON_DESPAWN_OBSERVER); - } - } -} - -/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event. -/// -/// This is stored inside of [`Observers`], specialized for each kind of observer. -#[derive(Default, Debug)] -pub struct CachedObservers { - // Observers listening for any time this event is fired, regardless of target - // This will also respond to events targeting specific components or entities - pub(super) global_observers: ObserverMap, - // Observers listening for this trigger fired at a specific component - pub(super) component_observers: HashMap, - // Observers listening for this trigger fired at a specific entity - pub(super) entity_observers: EntityHashMap, -} - -impl CachedObservers { - /// Returns the observers listening for this trigger, regardless of target. - /// These observers will also respond to events targeting specific components or entities. - pub fn global_observers(&self) -> &ObserverMap { - &self.global_observers - } - - /// Returns the observers listening for this trigger targeting components. - pub fn get_component_observers(&self) -> &HashMap { - &self.component_observers - } - - /// Returns the observers listening for this trigger targeting entities. - pub fn entity_observers(&self) -> &HashMap { - &self.component_observers - } -} - -/// Map between an observer entity and its [`ObserverRunner`] -pub type ObserverMap = EntityHashMap; - -/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event targeted at a specific component. -/// -/// This is stored inside of [`CachedObservers`]. -#[derive(Default, Debug)] -pub struct CachedComponentObservers { - // Observers listening to events targeting this component, but not a specific entity - pub(super) global_observers: ObserverMap, - // Observers listening to events targeting this component on a specific entity - pub(super) entity_component_observers: EntityHashMap, -} - -impl CachedComponentObservers { - /// Returns the observers listening for this trigger, regardless of target. - /// These observers will also respond to events targeting specific entities. - pub fn global_observers(&self) -> &ObserverMap { - &self.global_observers - } - - /// Returns the observers listening for this trigger targeting this component on a specific entity. - pub fn entity_component_observers(&self) -> &EntityHashMap { - &self.entity_component_observers - } -} diff --git a/src/observer/distributed_storage.rs b/src/observer/distributed_storage.rs deleted file mode 100644 index a9a3645..0000000 --- a/src/observer/distributed_storage.rs +++ /dev/null @@ -1,492 +0,0 @@ -//! Information about observers that is stored on the entities themselves. -//! -//! This allows for easier cleanup, better inspection, and more flexible querying. -//! -//! Each observer is associated with an entity, defined by the [`Observer`] component. -//! The [`Observer`] component contains the system that will be run when the observer is triggered, -//! and the [`ObserverDescriptor`] which contains information about what the observer is observing. -//! -//! When we watch entities, we add the [`ObservedBy`] component to those entities, -//! which links back to the observer entity. - -use core::any::Any; - -use crate::{ - component::{ComponentCloneBehavior, ComponentId, Mutable, StorageType}, - entity::Entity, - error::{ErrorContext, ErrorHandler}, - lifecycle::{ComponentHook, HookContext}, - observer::{observer_system_runner, ObserverRunner}, - prelude::*, - system::{IntoObserverSystem, ObserverSystem}, - world::DeferredWorld, -}; -use alloc::boxed::Box; -use alloc::vec::Vec; -use bevy_utils::prelude::DebugName; - -#[cfg(feature = "bevy_reflect")] -use crate::prelude::ReflectComponent; - -/// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to turn it into an "observer". -/// -/// Observers listen for a "trigger" of a specific [`Event`]. An event can be triggered on the [`World`] -/// by calling [`World::trigger`], or if the event is an [`EntityEvent`], it can also be triggered for specific -/// entity targets using [`World::trigger_targets`]. -/// -/// Note that [`BufferedEvent`]s sent using [`EventReader`] and [`EventWriter`] are _not_ automatically triggered. -/// They must be triggered at a specific point in the schedule. -/// -/// # Usage -/// -/// The simplest usage of the observer pattern looks like this: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # let mut world = World::default(); -/// #[derive(Event)] -/// struct Speak { -/// message: String, -/// } -/// -/// world.add_observer(|trigger: On| { -/// println!("{}", trigger.event().message); -/// }); -/// -/// // Observers currently require a flush() to be registered. In the context of schedules, -/// // this will generally be done for you. -/// world.flush(); -/// -/// world.trigger(Speak { -/// message: "Hello!".into(), -/// }); -/// ``` -/// -/// Notice that we used [`World::add_observer`]. This is just a shorthand for spawning an [`Observer`] manually: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # let mut world = World::default(); -/// # #[derive(Event)] -/// # struct Speak; -/// // These are functionally the same: -/// world.add_observer(|trigger: On| {}); -/// world.spawn(Observer::new(|trigger: On| {})); -/// ``` -/// -/// Observers are systems. They can access arbitrary [`World`] data by adding [`SystemParam`]s: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # let mut world = World::default(); -/// # #[derive(Event)] -/// # struct PrintNames; -/// # #[derive(Component, Debug)] -/// # struct Name; -/// world.add_observer(|trigger: On, names: Query<&Name>| { -/// for name in &names { -/// println!("{name:?}"); -/// } -/// }); -/// ``` -/// -/// Note that [`On`] must always be the first parameter. -/// -/// You can also add [`Commands`], which means you can spawn new entities, insert new components, etc: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # let mut world = World::default(); -/// # #[derive(Event)] -/// # struct SpawnThing; -/// # #[derive(Component, Debug)] -/// # struct Thing; -/// world.add_observer(|trigger: On, mut commands: Commands| { -/// commands.spawn(Thing); -/// }); -/// ``` -/// -/// Observers can also trigger new events: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # let mut world = World::default(); -/// # #[derive(Event)] -/// # struct A; -/// # #[derive(Event)] -/// # struct B; -/// world.add_observer(|trigger: On, mut commands: Commands| { -/// commands.trigger(B); -/// }); -/// ``` -/// -/// When the commands are flushed (including these "nested triggers") they will be -/// recursively evaluated until there are no commands left, meaning nested triggers all -/// evaluate at the same time! -/// -/// If the event is an [`EntityEvent`], it can be triggered for specific entities, -/// which will be passed to the [`Observer`]: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # let mut world = World::default(); -/// # let entity = world.spawn_empty().id(); -/// #[derive(Event, EntityEvent)] -/// struct Explode; -/// -/// world.add_observer(|trigger: On, mut commands: Commands| { -/// println!("Entity {} goes BOOM!", trigger.target()); -/// commands.entity(trigger.target()).despawn(); -/// }); -/// -/// world.flush(); -/// -/// world.trigger_targets(Explode, entity); -/// ``` -/// -/// You can trigger multiple entities at once: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # let mut world = World::default(); -/// # let e1 = world.spawn_empty().id(); -/// # let e2 = world.spawn_empty().id(); -/// # #[derive(Event, EntityEvent)] -/// # struct Explode; -/// world.trigger_targets(Explode, [e1, e2]); -/// ``` -/// -/// Observers can also watch _specific_ entities, which enables you to assign entity-specific logic: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # #[derive(Component, Debug)] -/// # struct Name(String); -/// # let mut world = World::default(); -/// # let e1 = world.spawn_empty().id(); -/// # let e2 = world.spawn_empty().id(); -/// # #[derive(Event, EntityEvent)] -/// # struct Explode; -/// world.entity_mut(e1).observe(|trigger: On, mut commands: Commands| { -/// println!("Boom!"); -/// commands.entity(trigger.target()).despawn(); -/// }); -/// -/// world.entity_mut(e2).observe(|trigger: On, mut commands: Commands| { -/// println!("The explosion fizzles! This entity is immune!"); -/// }); -/// ``` -/// -/// If all entities watched by a given [`Observer`] are despawned, the [`Observer`] entity will also be despawned. -/// This protects against observer "garbage" building up over time. -/// -/// The examples above calling [`EntityWorldMut::observe`] to add entity-specific observer logic are (once again) -/// just shorthand for spawning an [`Observer`] directly: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # let mut world = World::default(); -/// # let entity = world.spawn_empty().id(); -/// # #[derive(Event, EntityEvent)] -/// # struct Explode; -/// let mut observer = Observer::new(|trigger: On| {}); -/// observer.watch_entity(entity); -/// world.spawn(observer); -/// ``` -/// -/// Note that the [`Observer`] component is not added to the entity it is observing. Observers should always be their own entities! -/// -/// You can call [`Observer::watch_entity`] more than once, which allows you to watch multiple entities with the same [`Observer`]. -/// serves as the "source of truth" of the observer. -/// -/// [`SystemParam`]: crate::system::SystemParam -pub struct Observer { - hook_on_add: ComponentHook, - pub(crate) error_handler: Option, - pub(crate) system: Box, - pub(crate) descriptor: ObserverDescriptor, - pub(crate) last_trigger_id: u32, - pub(crate) despawned_watched_entities: u32, - pub(crate) runner: ObserverRunner, -} - -impl Observer { - /// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered - /// for _any_ entity (or no entity). - /// - /// # Panics - /// - /// Panics if the given system is an exclusive system. - pub fn new>(system: I) -> Self { - let system = Box::new(IntoObserverSystem::into_system(system)); - assert!( - !system.is_exclusive(), - concat!( - "Exclusive system `{}` may not be used as observer.\n", - "Instead of `&mut World`, use either `DeferredWorld` if you do not need structural changes, or `Commands` if you do." - ), - system.name() - ); - Self { - system, - descriptor: Default::default(), - hook_on_add: hook_on_add::, - error_handler: None, - runner: observer_system_runner::, - despawned_watched_entities: 0, - last_trigger_id: 0, - } - } - - /// Creates a new [`Observer`] with custom runner, this is mostly used for dynamic event observer - pub fn with_dynamic_runner(runner: ObserverRunner) -> Self { - Self { - system: Box::new(IntoSystem::into_system(|| {})), - descriptor: Default::default(), - hook_on_add: |mut world, hook_context| { - let default_error_handler = world.default_error_handler(); - world.commands().queue(move |world: &mut World| { - let entity = hook_context.entity; - if let Some(mut observe) = world.get_mut::(entity) { - if observe.descriptor.events.is_empty() { - return; - } - if observe.error_handler.is_none() { - observe.error_handler = Some(default_error_handler); - } - world.register_observer(entity); - } - }); - }, - error_handler: None, - runner, - despawned_watched_entities: 0, - last_trigger_id: 0, - } - } - - /// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered - /// for the `entity`. - pub fn with_entity(mut self, entity: Entity) -> Self { - self.descriptor.entities.push(entity); - self - } - - /// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered - /// for the `entity`. - /// Note that if this is called _after_ an [`Observer`] is spawned, it will produce no effects. - pub fn watch_entity(&mut self, entity: Entity) { - self.descriptor.entities.push(entity); - } - - /// Observe the given `component`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered - /// with the given component target. - pub fn with_component(mut self, component: ComponentId) -> Self { - self.descriptor.components.push(component); - self - } - - /// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`] - /// is triggered. - /// # Safety - /// The type of the `event` [`ComponentId`] _must_ match the actual value - /// of the event passed into the observer system. - pub unsafe fn with_event(mut self, event: ComponentId) -> Self { - self.descriptor.events.push(event); - self - } - - /// Set the error handler to use for this observer. - /// - /// See the [`error` module-level documentation](crate::error) for more information. - pub fn with_error_handler(mut self, error_handler: fn(BevyError, ErrorContext)) -> Self { - self.error_handler = Some(error_handler); - self - } - - /// Returns the [`ObserverDescriptor`] for this [`Observer`]. - pub fn descriptor(&self) -> &ObserverDescriptor { - &self.descriptor - } - - /// Returns the name of the [`Observer`]'s system . - pub fn system_name(&self) -> DebugName { - self.system.system_name() - } -} - -impl Component for Observer { - const STORAGE_TYPE: StorageType = StorageType::SparseSet; - type Mutability = Mutable; - fn on_add() -> Option { - Some(|world, context| { - let Some(observe) = world.get::(context.entity) else { - return; - }; - let hook = observe.hook_on_add; - hook(world, context); - }) - } - fn on_remove() -> Option { - Some(|mut world, HookContext { entity, .. }| { - let descriptor = core::mem::take( - &mut world - .entity_mut(entity) - .get_mut::() - .unwrap() - .as_mut() - .descriptor, - ); - world.commands().queue(move |world: &mut World| { - world.unregister_observer(entity, descriptor); - }); - }) - } -} - -/// Store information about what an [`Observer`] observes. -/// -/// This information is stored inside of the [`Observer`] component, -#[derive(Default, Clone)] -pub struct ObserverDescriptor { - /// The events the observer is watching. - pub(super) events: Vec, - - /// The components the observer is watching. - pub(super) components: Vec, - - /// The entities the observer is watching. - pub(super) entities: Vec, -} - -impl ObserverDescriptor { - /// Add the given `events` to the descriptor. - /// # Safety - /// The type of each [`ComponentId`] in `events` _must_ match the actual value - /// of the event passed into the observer. - pub unsafe fn with_events(mut self, events: Vec) -> Self { - self.events = events; - self - } - - /// Add the given `components` to the descriptor. - pub fn with_components(mut self, components: Vec) -> Self { - self.components = components; - self - } - - /// Add the given `entities` to the descriptor. - pub fn with_entities(mut self, entities: Vec) -> Self { - self.entities = entities; - self - } - - /// Returns the `events` that the observer is watching. - pub fn events(&self) -> &[ComponentId] { - &self.events - } - - /// Returns the `components` that the observer is watching. - pub fn components(&self) -> &[ComponentId] { - &self.components - } - - /// Returns the `entities` that the observer is watching. - pub fn entities(&self) -> &[Entity] { - &self.entities - } -} - -/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`crate::lifecycle::ComponentHooks::on_add`). -/// -/// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters -/// erased. -/// -/// The type parameters of this function _must_ match those used to create the [`Observer`]. -/// As such, it is recommended to only use this function within the [`Observer::new`] method to -/// ensure type parameters match. -fn hook_on_add>( - mut world: DeferredWorld<'_>, - HookContext { entity, .. }: HookContext, -) { - world.commands().queue(move |world: &mut World| { - let event_id = E::register_component_id(world); - let mut components = alloc::vec![]; - B::component_ids(&mut world.components_registrator(), &mut |id| { - components.push(id); - }); - if let Some(mut observer) = world.get_mut::(entity) { - observer.descriptor.events.push(event_id); - observer.descriptor.components.extend(components); - - let system: &mut dyn Any = observer.system.as_mut(); - let system: *mut dyn ObserverSystem = system.downcast_mut::().unwrap(); - // SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias - unsafe { - (*system).initialize(world); - } - world.register_observer(entity); - } - }); -} - -/// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to. -#[derive(Default, Debug)] -#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Component, Debug))] -pub struct ObservedBy(pub(crate) Vec); - -impl ObservedBy { - /// Provides a read-only reference to the list of entities observing this entity. - pub fn get(&self) -> &[Entity] { - &self.0 - } -} - -impl Component for ObservedBy { - const STORAGE_TYPE: StorageType = StorageType::SparseSet; - type Mutability = Mutable; - - fn on_remove() -> Option { - Some(|mut world, HookContext { entity, .. }| { - let observed_by = { - let mut component = world.get_mut::(entity).unwrap(); - core::mem::take(&mut component.0) - }; - for e in observed_by { - let (total_entities, despawned_watched_entities) = { - let Ok(mut entity_mut) = world.get_entity_mut(e) else { - continue; - }; - let Some(mut state) = entity_mut.get_mut::() else { - continue; - }; - state.despawned_watched_entities += 1; - ( - state.descriptor.entities.len(), - state.despawned_watched_entities as usize, - ) - }; - - // Despawn Observer if it has no more active sources. - if total_entities == despawned_watched_entities { - world.commands().entity(e).despawn(); - } - } - }) - } - - fn clone_behavior() -> ComponentCloneBehavior { - ComponentCloneBehavior::Ignore - } -} - -pub(crate) trait AnyNamedSystem: Any + Send + Sync + 'static { - fn system_name(&self) -> DebugName; -} - -impl AnyNamedSystem for T { - fn system_name(&self) -> DebugName { - self.name() - } -} diff --git a/src/observer/entity_cloning.rs b/src/observer/entity_cloning.rs deleted file mode 100644 index 7c7a4f6..0000000 --- a/src/observer/entity_cloning.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Logic to track observers when cloning entities. - -use crate::{ - component::ComponentCloneBehavior, - entity::{ - CloneByFilter, ComponentCloneCtx, EntityClonerBuilder, EntityMapper, SourceComponent, - }, - observer::ObservedBy, - world::World, -}; - -use super::Observer; - -impl EntityClonerBuilder<'_, Filter> { - /// Sets the option to automatically add cloned entities to the observers targeting source entity. - pub fn add_observers(&mut self, add_observers: bool) -> &mut Self { - if add_observers { - self.override_clone_behavior::(ComponentCloneBehavior::Custom( - component_clone_observed_by, - )) - } else { - self.remove_clone_behavior_override::() - } - } -} - -fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentCloneCtx) { - let target = ctx.target(); - let source = ctx.source(); - - ctx.queue_deferred(move |world: &mut World, _mapper: &mut dyn EntityMapper| { - let observed_by = world - .get::(source) - .map(|observed_by| observed_by.0.clone()) - .expect("Source entity must have ObservedBy"); - - world - .entity_mut(target) - .insert(ObservedBy(observed_by.clone())); - - for observer_entity in observed_by.iter().copied() { - let mut observer_state = world - .get_mut::(observer_entity) - .expect("Source observer entity must have Observer"); - observer_state.descriptor.entities.push(target); - let event_types = observer_state.descriptor.events.clone(); - let components = observer_state.descriptor.components.clone(); - for event_type in event_types { - let observers = world.observers.get_observers_mut(event_type); - if components.is_empty() { - if let Some(map) = observers.entity_observers.get(&source).cloned() { - observers.entity_observers.insert(target, map); - } - } else { - for component in &components { - let Some(observers) = observers.component_observers.get_mut(component) - else { - continue; - }; - if let Some(map) = - observers.entity_component_observers.get(&source).cloned() - { - observers.entity_component_observers.insert(target, map); - } - } - } - } - } - }); -} - -#[cfg(test)] -mod tests { - use crate::{ - entity::EntityCloner, - event::{EntityEvent, Event}, - observer::On, - resource::Resource, - system::ResMut, - world::World, - }; - - #[derive(Resource, Default)] - struct Num(usize); - - #[derive(Event, EntityEvent)] - struct E; - - #[test] - fn clone_entity_with_observer() { - let mut world = World::default(); - world.init_resource::(); - - let e = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| res.0 += 1) - .id(); - world.flush(); - - world.trigger_targets(E, e); - - let e_clone = world.spawn_empty().id(); - EntityCloner::build_opt_out(&mut world) - .add_observers(true) - .clone_entity(e, e_clone); - - world.trigger_targets(E, [e, e_clone]); - - assert_eq!(world.resource::().0, 3); - } -} diff --git a/src/observer/mod.rs b/src/observer/mod.rs deleted file mode 100644 index e9036ee..0000000 --- a/src/observer/mod.rs +++ /dev/null @@ -1,1441 +0,0 @@ -//! Observers are a push-based tool for responding to [`Event`]s. -//! -//! ## Observer targeting -//! -//! Observers can be "global", listening for events that are both targeted at and not targeted at any specific entity, -//! or they can be "entity-specific", listening for events that are targeted at specific entities. -//! -//! They can also be further refined by listening to events targeted at specific components -//! (instead of using a generic event type), as is done with the [`Add`] family of lifecycle events. -//! -//! When entities are observed, they will receive an [`ObservedBy`] component, -//! which will be updated to track the observers that are currently observing them. -//! -//! Currently, [observers cannot be retargeted after spawning](https://github.com/bevyengine/bevy/issues/19587): -//! despawn and respawn an observer as a workaround. -//! -//! ## Writing observers -//! -//! Observers are systems which implement [`IntoObserverSystem`] that listen for [`Event`]s matching their -//! type and target(s). -//! To write observer systems, use [`On`] as the first parameter of your system. -//! This parameter provides access to the specific event that triggered the observer, -//! as well as the entity that the event was targeted at, if any. -//! -//! Observers can request other data from the world, such as via a [`Query`] or [`Res`]. -//! Commonly, you might want to verify that the entity that the observable event is targeting -//! has a specific component, or meets some other condition. [`Query::get`] or [`Query::contains`] -//! on the [`On::target`] entity is a good way to do this. -//! -//! [`Commands`] can also be used inside of observers. -//! This can be particularly useful for triggering other observers! -//! -//! ## Spawning observers -//! -//! Observers can be spawned via [`World::add_observer`], or the equivalent app method. -//! This will cause an entity with the [`Observer`] component to be created, -//! which will then run the observer system whenever the event it is watching is triggered. -//! -//! You can control the targets that an observer is watching by calling [`Observer::watch_entity`] -//! once the entity is spawned, or by manually spawning an entity with the [`Observer`] component -//! configured with the desired targets. -//! -//! Observers are fundamentally defined as "entities which have the [`Observer`] component" -//! allowing you to add it manually to existing entities. -//! At first, this seems convenient, but only one observer can be added to an entity at a time, -//! regardless of the event it responds to: like always, components are unique. -//! -//! Instead, a better way to achieve a similar aim is to -//! use the [`EntityWorldMut::observe`] / [`EntityCommands::observe`] method, -//! which spawns a new observer, and configures it to watch the entity it is called on. -//! Unfortunately, observers defined in this way -//! [currently cannot be spawned as part of bundles](https://github.com/bevyengine/bevy/issues/14204). -//! -//! ## Triggering observers -//! -//! Observers are most commonly triggered by [`Commands`], -//! via [`Commands::trigger`] (for untargeted [`Event`]s) or [`Commands::trigger_targets`] (for targeted [`EntityEvent`]s). -//! Like usual, equivalent methods are available on [`World`], allowing you to reduce overhead when working with exclusive world access. -//! -//! If your observer is configured to watch for a specific component or set of components instead, -//! you can pass in [`ComponentId`]s into [`Commands::trigger_targets`] by using the [`TriggerTargets`] trait. -//! As discussed in the [`On`] documentation, this use case is rare, and is currently only used -//! for [lifecycle](crate::lifecycle) events, which are automatically emitted. -//! -//! ## Observer bubbling -//! -//! When using an [`EntityEvent`] targeted at an entity, the event can optionally be propagated to other targets, -//! typically up to parents in an entity hierarchy. -//! -//! This behavior is controlled via [`EntityEvent::Traversal`] and [`EntityEvent::AUTO_PROPAGATE`], -//! with the details of the propagation path specified by the [`Traversal`](crate::traversal::Traversal) trait. -//! -//! When auto-propagation is enabled, propagation must be manually stopped to prevent the event from -//! continuing to other targets. This can be done using the [`On::propagate`] method inside of your observer. -//! -//! ## Observer timing -//! -//! Observers are triggered via [`Commands`], which imply that they are evaluated at the next sync point in the ECS schedule. -//! Accordingly, they have full access to the world, and are evaluated sequentially, in the order that the commands were sent. -//! -//! To control the relative ordering of observers sent from different systems, -//! order the systems in the schedule relative to each other. -//! -//! Currently, Bevy does not provide [a way to specify the ordering of observers](https://github.com/bevyengine/bevy/issues/14890) -//! listening to the same event relative to each other. -//! -//! Commands sent by observers are [currently not immediately applied](https://github.com/bevyengine/bevy/issues/19569). -//! Instead, all queued observers will run, and then all of the commands from those observers will be applied. -//! Careful use of [`Schedule::apply_deferred`] may help as a workaround. -//! -//! ## Lifecycle events and observers -//! -//! It is important to note that observers, just like [hooks](crate::lifecycle::ComponentHooks), -//! can listen to and respond to [lifecycle](crate::lifecycle) events. -//! Unlike hooks, observers are not treated as an "innate" part of component behavior: -//! they can be added or removed at runtime, and multiple observers -//! can be registered for the same lifecycle event for the same component. -//! -//! The ordering of hooks versus observers differs based on the lifecycle event in question: -//! -//! - when adding components, hooks are evaluated first, then observers -//! - when removing components, observers are evaluated first, then hooks -//! -//! This allows hooks to act as constructors and destructors for components, -//! as they always have the first and final say in the component's lifecycle. -//! -//! ## Cleaning up observers -//! -//! Currently, observer entities are never cleaned up, even if their target entity(s) are despawned. -//! This won't cause any runtime overhead, but is a waste of memory and can result in memory leaks. -//! -//! If you run into this problem, you could manually scan the world for observer entities and despawn them, -//! by checking if the entity in [`Observer::descriptor`] still exists. -//! -//! ## Observers vs buffered events -//! -//! By contrast, [`EventReader`] and [`EventWriter`] ("buffered events"), are pull-based. -//! They require periodically polling the world to check for new events, typically in a system that runs as part of a schedule. -//! -//! This imposes a small overhead, making observers a better choice for extremely rare events, -//! but buffered events can be more efficient for events that are expected to occur multiple times per frame, -//! as it allows for batch processing of events. -//! -//! The difference in timing is also an important consideration: -//! buffered events are evaluated at fixed points during schedules, -//! while observers are evaluated as soon as possible after the event is triggered. -//! -//! This provides more control over the timing of buffered event evaluation, -//! but allows for a more ad hoc approach with observers, -//! and enables indefinite chaining of observers triggering other observers (for both better and worse!). - -mod centralized_storage; -mod distributed_storage; -mod entity_cloning; -mod runner; -mod system_param; -mod trigger_targets; - -pub use centralized_storage::*; -pub use distributed_storage::*; -pub use runner::*; -pub use system_param::*; -pub use trigger_targets::*; - -use crate::{ - change_detection::MaybeLocation, - component::ComponentId, - prelude::*, - system::IntoObserverSystem, - world::{DeferredWorld, *}, -}; - -impl World { - /// Spawns a "global" [`Observer`] which will watch for the given event. - /// Returns its [`Entity`] as a [`EntityWorldMut`]. - /// - /// `system` can be any system whose first parameter is [`On`]. - /// - /// **Calling [`observe`](EntityWorldMut::observe) on the returned - /// [`EntityWorldMut`] will observe the observer itself, which you very - /// likely do not want.** - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// #[derive(Component)] - /// struct A; - /// - /// # let mut world = World::new(); - /// world.add_observer(|_: On| { - /// // ... - /// }); - /// world.add_observer(|_: On| { - /// // ... - /// }); - /// ``` - /// - /// # Panics - /// - /// Panics if the given system is an exclusive system. - pub fn add_observer( - &mut self, - system: impl IntoObserverSystem, - ) -> EntityWorldMut { - self.spawn(Observer::new(system)) - } - - /// Triggers the given [`Event`], which will run any [`Observer`]s watching for it. - /// - /// While event types commonly implement [`Copy`], - /// those that don't will be consumed and will no longer be accessible. - /// If you need to use the event after triggering it, use [`World::trigger_ref`] instead. - #[track_caller] - pub fn trigger(&mut self, event: E) { - self.trigger_with_caller(event, MaybeLocation::caller()); - } - - pub(crate) fn trigger_with_caller(&mut self, mut event: E, caller: MaybeLocation) { - let event_id = E::register_component_id(self); - // SAFETY: We just registered `event_id` with the type of `event` - unsafe { - self.trigger_dynamic_ref_with_caller(event_id, &mut event, caller); - } - } - - /// Triggers the given [`Event`] as a mutable reference, which will run any [`Observer`]s watching for it. - /// - /// Compared to [`World::trigger`], this method is most useful when it's necessary to check - /// or use the event after it has been modified by observers. - #[track_caller] - pub fn trigger_ref(&mut self, event: &mut E) { - let event_id = E::register_component_id(self); - // SAFETY: We just registered `event_id` with the type of `event` - unsafe { self.trigger_dynamic_ref_with_caller(event_id, event, MaybeLocation::caller()) }; - } - - unsafe fn trigger_dynamic_ref_with_caller( - &mut self, - event_id: ComponentId, - event_data: &mut E, - caller: MaybeLocation, - ) { - let mut world = DeferredWorld::from(self); - // SAFETY: `event_data` is accessible as the type represented by `event_id` - unsafe { - world.trigger_observers_with_data::<_, ()>( - event_id, - None, - None, - core::iter::empty::(), - event_data, - false, - caller, - ); - }; - } - - /// Triggers the given [`EntityEvent`] for the given `targets`, which will run any [`Observer`]s watching for it. - /// - /// While event types commonly implement [`Copy`], - /// those that don't will be consumed and will no longer be accessible. - /// If you need to use the event after triggering it, use [`World::trigger_targets_ref`] instead. - #[track_caller] - pub fn trigger_targets(&mut self, event: E, targets: impl TriggerTargets) { - self.trigger_targets_with_caller(event, targets, MaybeLocation::caller()); - } - - pub(crate) fn trigger_targets_with_caller( - &mut self, - mut event: E, - targets: impl TriggerTargets, - caller: MaybeLocation, - ) { - let event_id = E::register_component_id(self); - // SAFETY: We just registered `event_id` with the type of `event` - unsafe { - self.trigger_targets_dynamic_ref_with_caller(event_id, &mut event, targets, caller); - } - } - - /// Triggers the given [`EntityEvent`] as a mutable reference for the given `targets`, - /// which will run any [`Observer`]s watching for it. - /// - /// Compared to [`World::trigger_targets`], this method is most useful when it's necessary to check - /// or use the event after it has been modified by observers. - #[track_caller] - pub fn trigger_targets_ref( - &mut self, - event: &mut E, - targets: impl TriggerTargets, - ) { - let event_id = E::register_component_id(self); - // SAFETY: We just registered `event_id` with the type of `event` - unsafe { self.trigger_targets_dynamic_ref(event_id, event, targets) }; - } - - /// Triggers the given [`EntityEvent`] for the given `targets`, which will run any [`Observer`]s watching for it. - /// - /// While event types commonly implement [`Copy`], - /// those that don't will be consumed and will no longer be accessible. - /// If you need to use the event after triggering it, use [`World::trigger_targets_dynamic_ref`] instead. - /// - /// # Safety - /// - /// Caller must ensure that `event_data` is accessible as the type represented by `event_id`. - #[track_caller] - pub unsafe fn trigger_targets_dynamic( - &mut self, - event_id: ComponentId, - mut event_data: E, - targets: Targets, - ) { - // SAFETY: `event_data` is accessible as the type represented by `event_id` - unsafe { - self.trigger_targets_dynamic_ref(event_id, &mut event_data, targets); - }; - } - - /// Triggers the given [`EntityEvent`] as a mutable reference for the given `targets`, - /// which will run any [`Observer`]s watching for it. - /// - /// Compared to [`World::trigger_targets_dynamic`], this method is most useful when it's necessary to check - /// or use the event after it has been modified by observers. - /// - /// # Safety - /// - /// Caller must ensure that `event_data` is accessible as the type represented by `event_id`. - #[track_caller] - pub unsafe fn trigger_targets_dynamic_ref( - &mut self, - event_id: ComponentId, - event_data: &mut E, - targets: Targets, - ) { - self.trigger_targets_dynamic_ref_with_caller( - event_id, - event_data, - targets, - MaybeLocation::caller(), - ); - } - - /// # Safety - /// - /// See `trigger_targets_dynamic_ref` - unsafe fn trigger_targets_dynamic_ref_with_caller( - &mut self, - event_id: ComponentId, - event_data: &mut E, - targets: Targets, - caller: MaybeLocation, - ) { - let mut world = DeferredWorld::from(self); - let mut entity_targets = targets.entities().peekable(); - if entity_targets.peek().is_none() { - // SAFETY: `event_data` is accessible as the type represented by `event_id` - unsafe { - world.trigger_observers_with_data::<_, E::Traversal>( - event_id, - None, - None, - targets.components(), - event_data, - false, - caller, - ); - }; - } else { - for target_entity in entity_targets { - // SAFETY: `event_data` is accessible as the type represented by `event_id` - unsafe { - world.trigger_observers_with_data::<_, E::Traversal>( - event_id, - Some(target_entity), - Some(target_entity), - targets.components(), - event_data, - E::AUTO_PROPAGATE, - caller, - ); - }; - } - } - } - - /// Register an observer to the cache, called when an observer is created - pub(crate) fn register_observer(&mut self, observer_entity: Entity) { - // SAFETY: References do not alias. - let (observer_state, archetypes, observers) = unsafe { - let observer_state: *const Observer = self.get::(observer_entity).unwrap(); - // Populate ObservedBy for each observed entity. - for watched_entity in (*observer_state).descriptor.entities.iter().copied() { - let mut entity_mut = self.entity_mut(watched_entity); - let mut observed_by = entity_mut.entry::().or_default().into_mut(); - observed_by.0.push(observer_entity); - } - (&*observer_state, &mut self.archetypes, &mut self.observers) - }; - let descriptor = &observer_state.descriptor; - - for &event_type in &descriptor.events { - let cache = observers.get_observers_mut(event_type); - - if descriptor.components.is_empty() && descriptor.entities.is_empty() { - cache - .global_observers - .insert(observer_entity, observer_state.runner); - } else if descriptor.components.is_empty() { - // Observer is not targeting any components so register it as an entity observer - for &watched_entity in &observer_state.descriptor.entities { - let map = cache.entity_observers.entry(watched_entity).or_default(); - map.insert(observer_entity, observer_state.runner); - } - } else { - // Register observer for each watched component - for &component in &descriptor.components { - let observers = - cache - .component_observers - .entry(component) - .or_insert_with(|| { - if let Some(flag) = Observers::is_archetype_cached(event_type) { - archetypes.update_flags(component, flag, true); - } - CachedComponentObservers::default() - }); - if descriptor.entities.is_empty() { - // Register for all triggers targeting the component - observers - .global_observers - .insert(observer_entity, observer_state.runner); - } else { - // Register for each watched entity - for &watched_entity in &descriptor.entities { - let map = observers - .entity_component_observers - .entry(watched_entity) - .or_default(); - map.insert(observer_entity, observer_state.runner); - } - } - } - } - } - } - - /// Remove the observer from the cache, called when an observer gets despawned - pub(crate) fn unregister_observer(&mut self, entity: Entity, descriptor: ObserverDescriptor) { - let archetypes = &mut self.archetypes; - let observers = &mut self.observers; - - for &event_type in &descriptor.events { - let cache = observers.get_observers_mut(event_type); - if descriptor.components.is_empty() && descriptor.entities.is_empty() { - cache.global_observers.remove(&entity); - } else if descriptor.components.is_empty() { - for watched_entity in &descriptor.entities { - // This check should be unnecessary since this observer hasn't been unregistered yet - let Some(observers) = cache.entity_observers.get_mut(watched_entity) else { - continue; - }; - observers.remove(&entity); - if observers.is_empty() { - cache.entity_observers.remove(watched_entity); - } - } - } else { - for component in &descriptor.components { - let Some(observers) = cache.component_observers.get_mut(component) else { - continue; - }; - if descriptor.entities.is_empty() { - observers.global_observers.remove(&entity); - } else { - for watched_entity in &descriptor.entities { - let Some(map) = - observers.entity_component_observers.get_mut(watched_entity) - else { - continue; - }; - map.remove(&entity); - if map.is_empty() { - observers.entity_component_observers.remove(watched_entity); - } - } - } - - if observers.global_observers.is_empty() - && observers.entity_component_observers.is_empty() - { - cache.component_observers.remove(component); - if let Some(flag) = Observers::is_archetype_cached(event_type) { - if let Some(by_component) = archetypes.by_component.get(component) { - for archetype in by_component.keys() { - let archetype = &mut archetypes.archetypes[archetype.index()]; - if archetype.contains(*component) { - let no_longer_observed = archetype - .components() - .all(|id| !cache.component_observers.contains_key(&id)); - - if no_longer_observed { - archetype.flags.set(flag, false); - } - } - } - } - } - } - } - } - } - } -} - -#[cfg(test)] -mod tests { - use alloc::{vec, vec::Vec}; - - use bevy_platform::collections::HashMap; - use bevy_ptr::OwningPtr; - - use crate::component::ComponentId; - use crate::{ - change_detection::MaybeLocation, - observer::{Observer, Replace}, - prelude::*, - traversal::Traversal, - }; - - #[derive(Component)] - struct A; - - #[derive(Component)] - struct B; - - #[derive(Component)] - struct C; - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct S; - - #[derive(Event, EntityEvent)] - struct EventA; - - #[derive(Event, EntityEvent)] - struct EventWithData { - counter: usize, - } - - #[derive(Resource, Default)] - struct Order(Vec<&'static str>); - - impl Order { - #[track_caller] - fn observed(&mut self, name: &'static str) { - self.0.push(name); - } - } - - #[derive(Component)] - struct ChildOf(Entity); - - impl Traversal for &'_ ChildOf { - fn traverse(item: Self::Item<'_, '_>, _: &D) -> Option { - Some(item.0) - } - } - - #[derive(Component, Event, EntityEvent)] - #[entity_event(traversal = &'static ChildOf, auto_propagate)] - struct EventPropagating; - - #[test] - fn observer_order_spawn_despawn() { - let mut world = World::new(); - world.init_resource::(); - - world.add_observer(|_: On, mut res: ResMut| res.observed("add")); - world.add_observer(|_: On, mut res: ResMut| res.observed("insert")); - world.add_observer(|_: On, mut res: ResMut| { - res.observed("replace"); - }); - world.add_observer(|_: On, mut res: ResMut| res.observed("remove")); - - let entity = world.spawn(A).id(); - world.despawn(entity); - assert_eq!( - vec!["add", "insert", "replace", "remove"], - world.resource::().0 - ); - } - - #[test] - fn observer_order_insert_remove() { - let mut world = World::new(); - world.init_resource::(); - - world.add_observer(|_: On, mut res: ResMut| res.observed("add")); - world.add_observer(|_: On, mut res: ResMut| res.observed("insert")); - world.add_observer(|_: On, mut res: ResMut| { - res.observed("replace"); - }); - world.add_observer(|_: On, mut res: ResMut| res.observed("remove")); - - let mut entity = world.spawn_empty(); - entity.insert(A); - entity.remove::(); - entity.flush(); - assert_eq!( - vec!["add", "insert", "replace", "remove"], - world.resource::().0 - ); - } - - #[test] - fn observer_order_insert_remove_sparse() { - let mut world = World::new(); - world.init_resource::(); - - world.add_observer(|_: On, mut res: ResMut| res.observed("add")); - world.add_observer(|_: On, mut res: ResMut| res.observed("insert")); - world.add_observer(|_: On, mut res: ResMut| { - res.observed("replace"); - }); - world.add_observer(|_: On, mut res: ResMut| res.observed("remove")); - - let mut entity = world.spawn_empty(); - entity.insert(S); - entity.remove::(); - entity.flush(); - assert_eq!( - vec!["add", "insert", "replace", "remove"], - world.resource::().0 - ); - } - - #[test] - fn observer_order_replace() { - let mut world = World::new(); - world.init_resource::(); - - let entity = world.spawn(A).id(); - - world.add_observer(|_: On, mut res: ResMut| res.observed("add")); - world.add_observer(|_: On, mut res: ResMut| res.observed("insert")); - world.add_observer(|_: On, mut res: ResMut| { - res.observed("replace"); - }); - world.add_observer(|_: On, mut res: ResMut| res.observed("remove")); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - - let mut entity = world.entity_mut(entity); - entity.insert(A); - entity.flush(); - assert_eq!(vec!["replace", "insert"], world.resource::().0); - } - - #[test] - fn observer_order_recursive() { - let mut world = World::new(); - world.init_resource::(); - world.add_observer( - |obs: On, mut res: ResMut, mut commands: Commands| { - res.observed("add_a"); - commands.entity(obs.target()).insert(B); - }, - ); - world.add_observer( - |obs: On, mut res: ResMut, mut commands: Commands| { - res.observed("remove_a"); - commands.entity(obs.target()).remove::(); - }, - ); - - world.add_observer( - |obs: On, mut res: ResMut, mut commands: Commands| { - res.observed("add_b"); - commands.entity(obs.target()).remove::(); - }, - ); - world.add_observer(|_: On, mut res: ResMut| { - res.observed("remove_b"); - }); - - let entity = world.spawn(A).flush(); - let entity = world.get_entity(entity).unwrap(); - assert!(!entity.contains::()); - assert!(!entity.contains::()); - assert_eq!( - vec!["add_a", "add_b", "remove_a", "remove_b"], - world.resource::().0 - ); - } - - #[test] - fn observer_trigger_ref() { - let mut world = World::new(); - - world.add_observer(|mut trigger: On| trigger.event_mut().counter += 1); - world.add_observer(|mut trigger: On| trigger.event_mut().counter += 2); - world.add_observer(|mut trigger: On| trigger.event_mut().counter += 4); - // This flush is required for the last observer to be called when triggering the event, - // due to `World::add_observer` returning `WorldEntityMut`. - world.flush(); - - let mut event = EventWithData { counter: 0 }; - world.trigger_ref(&mut event); - assert_eq!(7, event.counter); - } - - #[test] - fn observer_trigger_targets_ref() { - let mut world = World::new(); - - world.add_observer(|mut trigger: On| { - trigger.event_mut().counter += 1; - }); - world.add_observer(|mut trigger: On| { - trigger.event_mut().counter += 2; - }); - world.add_observer(|mut trigger: On| { - trigger.event_mut().counter += 4; - }); - // This flush is required for the last observer to be called when triggering the event, - // due to `World::add_observer` returning `WorldEntityMut`. - world.flush(); - - let mut event = EventWithData { counter: 0 }; - let component_a = world.register_component::(); - world.trigger_targets_ref(&mut event, component_a); - assert_eq!(5, event.counter); - } - - #[test] - fn observer_multiple_listeners() { - let mut world = World::new(); - world.init_resource::(); - - world.add_observer(|_: On, mut res: ResMut| res.observed("add_1")); - world.add_observer(|_: On, mut res: ResMut| res.observed("add_2")); - - world.spawn(A).flush(); - assert_eq!(vec!["add_2", "add_1"], world.resource::().0); - // Our A entity plus our two observers - assert_eq!(world.entity_count(), 3); - } - - #[test] - fn observer_multiple_events() { - let mut world = World::new(); - world.init_resource::(); - let on_remove = Remove::register_component_id(&mut world); - world.spawn( - // SAFETY: Add and Remove are both unit types, so this is safe - unsafe { - Observer::new(|_: On, mut res: ResMut| { - res.observed("add/remove"); - }) - .with_event(on_remove) - }, - ); - - let entity = world.spawn(A).id(); - world.despawn(entity); - assert_eq!( - vec!["add/remove", "add/remove"], - world.resource::().0 - ); - } - - #[test] - fn observer_multiple_components() { - let mut world = World::new(); - world.init_resource::(); - world.register_component::(); - world.register_component::(); - - world.add_observer(|_: On, mut res: ResMut| { - res.observed("add_ab"); - }); - - let entity = world.spawn(A).id(); - world.entity_mut(entity).insert(B); - world.flush(); - assert_eq!(vec!["add_ab", "add_ab"], world.resource::().0); - } - - #[test] - fn observer_despawn() { - let mut world = World::new(); - - let system: fn(On) = |_| { - panic!("Observer triggered after being despawned."); - }; - let observer = world.add_observer(system).id(); - world.despawn(observer); - world.spawn(A).flush(); - } - - // Regression test for https://github.com/bevyengine/bevy/issues/14961 - #[test] - fn observer_despawn_archetype_flags() { - let mut world = World::new(); - world.init_resource::(); - - let entity = world.spawn((A, B)).flush(); - - world.add_observer(|_: On, mut res: ResMut| { - res.observed("remove_a"); - }); - - let system: fn(On) = |_: On| { - panic!("Observer triggered after being despawned."); - }; - - let observer = world.add_observer(system).flush(); - world.despawn(observer); - - world.despawn(entity); - - assert_eq!(vec!["remove_a"], world.resource::().0); - } - - #[test] - fn observer_multiple_matches() { - let mut world = World::new(); - world.init_resource::(); - - world.add_observer(|_: On, mut res: ResMut| { - res.observed("add_ab"); - }); - - world.spawn((A, B)).flush(); - assert_eq!(vec!["add_ab"], world.resource::().0); - } - - #[test] - fn observer_no_target() { - let mut world = World::new(); - world.init_resource::(); - - let system: fn(On) = |_| { - panic!("Trigger routed to non-targeted entity."); - }; - world.spawn_empty().observe(system); - world.add_observer(move |obs: On, mut res: ResMut| { - assert_eq!(obs.target(), Entity::PLACEHOLDER); - res.observed("event_a"); - }); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger(EventA); - world.flush(); - assert_eq!(vec!["event_a"], world.resource::().0); - } - - #[test] - fn observer_entity_routing() { - let mut world = World::new(); - world.init_resource::(); - - let system: fn(On) = |_| { - panic!("Trigger routed to non-targeted entity."); - }; - - world.spawn_empty().observe(system); - let entity = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| res.observed("a_1")) - .id(); - world.add_observer(move |obs: On, mut res: ResMut| { - assert_eq!(obs.target(), entity); - res.observed("a_2"); - }); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger_targets(EventA, entity); - world.flush(); - assert_eq!(vec!["a_2", "a_1"], world.resource::().0); - } - - #[test] - fn observer_multiple_targets() { - #[derive(Resource, Default)] - struct R(i32); - - let mut world = World::new(); - let component_a = world.register_component::(); - let component_b = world.register_component::(); - world.init_resource::(); - - // targets (entity_1, A) - let entity_1 = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| res.0 += 1) - .id(); - // targets (entity_2, B) - let entity_2 = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| res.0 += 10) - .id(); - // targets any entity or component - world.add_observer(|_: On, mut res: ResMut| res.0 += 100); - // targets any entity, and components A or B - world.add_observer(|_: On, mut res: ResMut| res.0 += 1000); - // test all tuples - world.add_observer(|_: On, mut res: ResMut| res.0 += 10000); - world.add_observer( - |_: On, mut res: ResMut| { - res.0 += 100000; - }, - ); - world.add_observer( - |_: On, - mut res: ResMut| res.0 += 1000000, - ); - - // WorldEntityMut does not automatically flush. - world.flush(); - - // trigger for an entity and a component - world.trigger_targets(EventA, (entity_1, component_a)); - world.flush(); - // only observer that doesn't trigger is the one only watching entity_2 - assert_eq!(1111101, world.resource::().0); - world.resource_mut::().0 = 0; - - // trigger for both entities, but no components: trigger once per entity target - world.trigger_targets(EventA, (entity_1, entity_2)); - world.flush(); - // only the observer that doesn't require components triggers - once per entity - assert_eq!(200, world.resource::().0); - world.resource_mut::().0 = 0; - - // trigger for both components, but no entities: trigger once - world.trigger_targets(EventA, (component_a, component_b)); - world.flush(); - // all component observers trigger, entities are not observed - assert_eq!(1111100, world.resource::().0); - world.resource_mut::().0 = 0; - - // trigger for both entities and both components: trigger once per entity target - // we only get 2222211 because a given observer can trigger only once per entity target - world.trigger_targets(EventA, ((component_a, component_b), (entity_1, entity_2))); - world.flush(); - assert_eq!(2222211, world.resource::().0); - world.resource_mut::().0 = 0; - - // trigger to test complex tuples: (A, B, (A, B)) - world.trigger_targets( - EventA, - (component_a, component_b, (component_a, component_b)), - ); - world.flush(); - // the duplicate components in the tuple don't cause multiple triggers - assert_eq!(1111100, world.resource::().0); - world.resource_mut::().0 = 0; - - // trigger to test complex tuples: (A, B, (A, B), ((A, B), (A, B))) - world.trigger_targets( - EventA, - ( - component_a, - component_b, - (component_a, component_b), - ((component_a, component_b), (component_a, component_b)), - ), - ); - world.flush(); - // the duplicate components in the tuple don't cause multiple triggers - assert_eq!(1111100, world.resource::().0); - world.resource_mut::().0 = 0; - - // trigger to test the most complex tuple: (A, B, (A, B), (B, A), (A, B, ((A, B), (B, A)))) - world.trigger_targets( - EventA, - ( - component_a, - component_b, - (component_a, component_b), - (component_b, component_a), - ( - component_a, - component_b, - ((component_a, component_b), (component_b, component_a)), - ), - ), - ); - world.flush(); - // the duplicate components in the tuple don't cause multiple triggers - assert_eq!(1111100, world.resource::().0); - world.resource_mut::().0 = 0; - } - - #[test] - fn observer_dynamic_component() { - let mut world = World::new(); - world.init_resource::(); - - let component_id = world.register_component::(); - world.spawn( - Observer::new(|_: On, mut res: ResMut| res.observed("event_a")) - .with_component(component_id), - ); - - let mut entity = world.spawn_empty(); - OwningPtr::make(A, |ptr| { - // SAFETY: we registered `component_id` above. - unsafe { entity.insert_by_id(component_id, ptr) }; - }); - let entity = entity.flush(); - - world.trigger_targets(EventA, entity); - world.flush(); - assert_eq!(vec!["event_a"], world.resource::().0); - } - - #[test] - fn observer_dynamic_trigger() { - let mut world = World::new(); - world.init_resource::(); - let event_a = Remove::register_component_id(&mut world); - - // SAFETY: we registered `event_a` above and it matches the type of EventA - let observe = unsafe { - Observer::with_dynamic_runner(|mut world, _trigger, _ptr, _propagate| { - world.resource_mut::().observed("event_a"); - }) - .with_event(event_a) - }; - world.spawn(observe); - - world.commands().queue(move |world: &mut World| { - // SAFETY: we registered `event_a` above and it matches the type of EventA - unsafe { world.trigger_targets_dynamic(event_a, EventA, ()) }; - }); - world.flush(); - assert_eq!(vec!["event_a"], world.resource::().0); - } - - #[test] - fn observer_propagating() { - let mut world = World::new(); - world.init_resource::(); - - let parent = world.spawn_empty().id(); - let child = world.spawn(ChildOf(parent)).id(); - - world.entity_mut(parent).observe( - move |trigger: On, mut res: ResMut| { - res.observed("parent"); - - assert_eq!(trigger.target(), parent); - assert_eq!(trigger.original_target(), child); - }, - ); - - world.entity_mut(child).observe( - move |trigger: On, mut res: ResMut| { - res.observed("child"); - assert_eq!(trigger.target(), child); - assert_eq!(trigger.original_target(), child); - }, - ); - - // TODO: ideally this flush is not necessary, but right now observe() returns EntityWorldMut - // and therefore does not automatically flush. - world.flush(); - world.trigger_targets(EventPropagating, child); - world.flush(); - assert_eq!(vec!["child", "parent"], world.resource::().0); - } - - #[test] - fn observer_propagating_redundant_dispatch_same_entity() { - let mut world = World::new(); - world.init_resource::(); - - let parent = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| { - res.observed("parent"); - }) - .id(); - - let child = world - .spawn(ChildOf(parent)) - .observe(|_: On, mut res: ResMut| { - res.observed("child"); - }) - .id(); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger_targets(EventPropagating, [child, child]); - world.flush(); - assert_eq!( - vec!["child", "parent", "child", "parent"], - world.resource::().0 - ); - } - - #[test] - fn observer_propagating_redundant_dispatch_parent_child() { - let mut world = World::new(); - world.init_resource::(); - - let parent = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| { - res.observed("parent"); - }) - .id(); - - let child = world - .spawn(ChildOf(parent)) - .observe(|_: On, mut res: ResMut| { - res.observed("child"); - }) - .id(); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger_targets(EventPropagating, [child, parent]); - world.flush(); - assert_eq!( - vec!["child", "parent", "parent"], - world.resource::().0 - ); - } - - #[test] - fn observer_propagating_halt() { - let mut world = World::new(); - world.init_resource::(); - - let parent = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| { - res.observed("parent"); - }) - .id(); - - let child = world - .spawn(ChildOf(parent)) - .observe( - |mut trigger: On, mut res: ResMut| { - res.observed("child"); - trigger.propagate(false); - }, - ) - .id(); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger_targets(EventPropagating, child); - world.flush(); - assert_eq!(vec!["child"], world.resource::().0); - } - - #[test] - fn observer_propagating_join() { - let mut world = World::new(); - world.init_resource::(); - - let parent = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| { - res.observed("parent"); - }) - .id(); - - let child_a = world - .spawn(ChildOf(parent)) - .observe(|_: On, mut res: ResMut| { - res.observed("child_a"); - }) - .id(); - - let child_b = world - .spawn(ChildOf(parent)) - .observe(|_: On, mut res: ResMut| { - res.observed("child_b"); - }) - .id(); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger_targets(EventPropagating, [child_a, child_b]); - world.flush(); - assert_eq!( - vec!["child_a", "parent", "child_b", "parent"], - world.resource::().0 - ); - } - - #[test] - fn observer_propagating_no_next() { - let mut world = World::new(); - world.init_resource::(); - - let entity = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| { - res.observed("event"); - }) - .id(); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger_targets(EventPropagating, entity); - world.flush(); - assert_eq!(vec!["event"], world.resource::().0); - } - - #[test] - fn observer_propagating_parallel_propagation() { - let mut world = World::new(); - world.init_resource::(); - - let parent_a = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| { - res.observed("parent_a"); - }) - .id(); - - let child_a = world - .spawn(ChildOf(parent_a)) - .observe( - |mut trigger: On, mut res: ResMut| { - res.observed("child_a"); - trigger.propagate(false); - }, - ) - .id(); - - let parent_b = world - .spawn_empty() - .observe(|_: On, mut res: ResMut| { - res.observed("parent_b"); - }) - .id(); - - let child_b = world - .spawn(ChildOf(parent_b)) - .observe(|_: On, mut res: ResMut| { - res.observed("child_b"); - }) - .id(); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger_targets(EventPropagating, [child_a, child_b]); - world.flush(); - assert_eq!( - vec!["child_a", "child_b", "parent_b"], - world.resource::().0 - ); - } - - #[test] - fn observer_propagating_world() { - let mut world = World::new(); - world.init_resource::(); - - world.add_observer(|_: On, mut res: ResMut| { - res.observed("event"); - }); - - let grandparent = world.spawn_empty().id(); - let parent = world.spawn(ChildOf(grandparent)).id(); - let child = world.spawn(ChildOf(parent)).id(); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger_targets(EventPropagating, child); - world.flush(); - assert_eq!(vec!["event", "event", "event"], world.resource::().0); - } - - #[test] - fn observer_propagating_world_skipping() { - let mut world = World::new(); - world.init_resource::(); - - world.add_observer( - |trigger: On, query: Query<&A>, mut res: ResMut| { - if query.get(trigger.target()).is_ok() { - res.observed("event"); - } - }, - ); - - let grandparent = world.spawn(A).id(); - let parent = world.spawn(ChildOf(grandparent)).id(); - let child = world.spawn((A, ChildOf(parent))).id(); - - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger_targets(EventPropagating, child); - world.flush(); - assert_eq!(vec!["event", "event"], world.resource::().0); - } - - // Originally for https://github.com/bevyengine/bevy/issues/18452 - #[test] - fn observer_modifies_relationship() { - fn on_add(trigger: On, mut commands: Commands) { - commands - .entity(trigger.target()) - .with_related_entities::(|rsc| { - rsc.spawn_empty(); - }); - } - - let mut world = World::new(); - world.add_observer(on_add); - world.spawn(A); - world.flush(); - } - - // Regression test for https://github.com/bevyengine/bevy/issues/14467 - // Fails prior to https://github.com/bevyengine/bevy/pull/15398 - #[test] - fn observer_on_remove_during_despawn_spawn_empty() { - let mut world = World::new(); - - // Observe the removal of A - this will run during despawn - world.add_observer(|_: On, mut cmd: Commands| { - // Spawn a new entity - this reserves a new ID and requires a flush - // afterward before Entities::free can be called. - cmd.spawn_empty(); - }); - - let ent = world.spawn(A).id(); - - // Despawn our entity, which runs the Remove observer and allocates a - // new Entity. - // Should not panic - if it does, then Entities was not flushed properly - // after the observer's spawn_empty. - world.despawn(ent); - } - - #[test] - #[should_panic] - fn observer_invalid_params() { - #[derive(Resource)] - struct ResA; - - #[derive(Resource)] - struct ResB; - - let mut world = World::new(); - // This fails because `ResA` is not present in the world - world.add_observer(|_: On, _: Res, mut commands: Commands| { - commands.insert_resource(ResB); - }); - world.trigger(EventA); - } - - #[test] - fn observer_apply_deferred_from_param_set() { - #[derive(Resource)] - struct ResA; - - let mut world = World::new(); - world.add_observer( - |_: On, mut params: ParamSet<(Query, Commands)>| { - params.p1().insert_resource(ResA); - }, - ); - // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut - // and therefore does not automatically flush. - world.flush(); - world.trigger(EventA); - world.flush(); - - assert!(world.get_resource::().is_some()); - } - - #[test] - #[track_caller] - fn observer_caller_location_event() { - #[derive(Event)] - struct EventA; - - let caller = MaybeLocation::caller(); - let mut world = World::new(); - world.add_observer(move |trigger: On| { - assert_eq!(trigger.caller(), caller); - }); - world.trigger(EventA); - } - - #[test] - #[track_caller] - fn observer_caller_location_command_archetype_move() { - #[derive(Component)] - struct Component; - - let caller = MaybeLocation::caller(); - let mut world = World::new(); - world.add_observer(move |trigger: On| { - assert_eq!(trigger.caller(), caller); - }); - world.add_observer(move |trigger: On| { - assert_eq!(trigger.caller(), caller); - }); - world.commands().spawn(Component).clear(); - world.flush(); - } - - #[test] - fn observer_triggered_components() { - #[derive(Resource, Default)] - struct Counter(HashMap); - - let mut world = World::new(); - world.init_resource::(); - let a_id = world.register_component::(); - let b_id = world.register_component::(); - - world.add_observer( - |trigger: On, mut counter: ResMut| { - for &component in trigger.components() { - *counter.0.entry(component).or_default() += 1; - } - }, - ); - world.flush(); - - world.trigger_targets(EventA, [a_id, b_id]); - world.trigger_targets(EventA, a_id); - world.trigger_targets(EventA, b_id); - world.trigger_targets(EventA, [a_id, b_id]); - world.trigger_targets(EventA, a_id); - world.flush(); - - let counter = world.resource::(); - assert_eq!(4, *counter.0.get(&a_id).unwrap()); - assert_eq!(3, *counter.0.get(&b_id).unwrap()); - } -} diff --git a/src/observer/runner.rs b/src/observer/runner.rs deleted file mode 100644 index f25e742..0000000 --- a/src/observer/runner.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! Logic for evaluating observers, and storing functions inside of observers. - -use core::any::Any; - -use crate::{ - error::ErrorContext, - observer::ObserverTrigger, - prelude::*, - query::DebugCheckedUnwrap, - system::{ObserverSystem, RunSystemError}, - world::DeferredWorld, -}; -use bevy_ptr::PtrMut; - -/// Type for function that is run when an observer is triggered. -/// -/// Typically refers to the default runner that runs the system stored in the associated [`Observer`] component, -/// but can be overridden for custom behavior. -pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate: &mut bool); - -pub(super) fn observer_system_runner>( - mut world: DeferredWorld, - observer_trigger: ObserverTrigger, - ptr: PtrMut, - propagate: &mut bool, -) { - let world = world.as_unsafe_world_cell(); - // SAFETY: Observer was triggered so must still exist in world - let observer_cell = unsafe { - world - .get_entity(observer_trigger.observer) - .debug_checked_unwrap() - }; - // SAFETY: Observer was triggered so must have an `Observer` - let mut state = unsafe { observer_cell.get_mut::().debug_checked_unwrap() }; - - // TODO: Move this check into the observer cache to avoid dynamic dispatch - let last_trigger = world.last_trigger_id(); - if state.last_trigger_id == last_trigger { - return; - } - state.last_trigger_id = last_trigger; - - let trigger: On = On::new( - // SAFETY: Caller ensures `ptr` is castable to `&mut T` - unsafe { ptr.deref_mut() }, - propagate, - observer_trigger, - ); - - // SAFETY: - // - observer was triggered so must have an `Observer` component. - // - observer cannot be dropped or mutated until after the system pointer is already dropped. - let system: *mut dyn ObserverSystem = unsafe { - let system: &mut dyn Any = state.system.as_mut(); - let system = system.downcast_mut::().debug_checked_unwrap(); - &mut *system - }; - - // SAFETY: - // - there are no outstanding references to world except a private component - // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` - // and is never exclusive - // - system is the same type erased system from above - unsafe { - // Always refresh hotpatch pointers - // There's no guarantee that the `HotPatched` event would still be there once the observer is triggered. - #[cfg(feature = "hotpatching")] - (*system).refresh_hotpatch(); - - if let Err(RunSystemError::Failed(err)) = (*system) - .validate_param_unsafe(world) - .map_err(From::from) - .and_then(|()| (*system).run_unsafe(trigger, world)) - { - let handler = state - .error_handler - .unwrap_or_else(|| world.default_error_handler()); - handler( - err, - ErrorContext::Observer { - name: (*system).name(), - last_run: (*system).get_last_run(), - }, - ); - }; - (*system).queue_deferred(world.into_deferred()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - error::{ignore, DefaultErrorHandler}, - event::Event, - observer::On, - }; - - #[derive(Event)] - struct TriggerEvent; - - #[test] - #[should_panic(expected = "I failed!")] - fn test_fallible_observer() { - fn system(_: On) -> Result { - Err("I failed!".into()) - } - - let mut world = World::default(); - world.add_observer(system); - Schedule::default().run(&mut world); - world.trigger(TriggerEvent); - } - - #[test] - fn test_fallible_observer_ignored_errors() { - #[derive(Resource, Default)] - struct Ran(bool); - - fn system(_: On, mut ran: ResMut) -> Result { - ran.0 = true; - Err("I failed!".into()) - } - - // Using observer error handler - let mut world = World::default(); - world.init_resource::(); - world.spawn(Observer::new(system).with_error_handler(ignore)); - world.trigger(TriggerEvent); - assert!(world.resource::().0); - - // Using world error handler - let mut world = World::default(); - world.init_resource::(); - world.spawn(Observer::new(system)); - // Test that the correct handler is used when the observer was added - // before the default handler - world.insert_resource(DefaultErrorHandler(ignore)); - world.trigger(TriggerEvent); - assert!(world.resource::().0); - } - - #[test] - #[should_panic] - fn exclusive_system_cannot_be_observer() { - fn system(_: On, _world: &mut World) {} - let mut world = World::default(); - world.add_observer(system); - } -} diff --git a/src/observer/system_param.rs b/src/observer/system_param.rs deleted file mode 100644 index 27d6fef..0000000 --- a/src/observer/system_param.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! System parameters for working with observers. - -use core::marker::PhantomData; -use core::ops::DerefMut; -use core::{fmt::Debug, ops::Deref}; - -use bevy_ptr::Ptr; -use smallvec::SmallVec; - -use crate::{ - bundle::Bundle, change_detection::MaybeLocation, component::ComponentId, event::EntityEvent, - prelude::*, -}; - -/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the -/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. It also -/// contains event propagation information. See [`On::propagate`] for more information. -/// -/// The generic `B: Bundle` is used to modify the further specialize the events that this observer is interested in. -/// The entity involved *does not* have to have these components, but the observer will only be -/// triggered if the event matches the components in `B`. -/// -/// This is used to to avoid providing a generic argument in your event, as is done for [`Add`] -/// and the other lifecycle events. -/// -/// Providing multiple components in this bundle will cause this event to be triggered by any -/// matching component in the bundle, -/// [rather than requiring all of them to be present](https://github.com/bevyengine/bevy/issues/15325). -pub struct On<'w, E, B: Bundle = ()> { - event: &'w mut E, - propagate: &'w mut bool, - trigger: ObserverTrigger, - _marker: PhantomData, -} - -/// Deprecated in favor of [`On`]. -#[deprecated(since = "0.17.0", note = "Renamed to `On`.")] -pub type Trigger<'w, E, B = ()> = On<'w, E, B>; - -impl<'w, E, B: Bundle> On<'w, E, B> { - /// Creates a new instance of [`On`] for the given event and observer information. - pub fn new(event: &'w mut E, propagate: &'w mut bool, trigger: ObserverTrigger) -> Self { - Self { - event, - propagate, - trigger, - _marker: PhantomData, - } - } - - /// Returns the event type of this [`On`] instance. - pub fn event_type(&self) -> ComponentId { - self.trigger.event_type - } - - /// Returns a reference to the triggered event. - pub fn event(&self) -> &E { - self.event - } - - /// Returns a mutable reference to the triggered event. - pub fn event_mut(&mut self) -> &mut E { - self.event - } - - /// Returns a pointer to the triggered event. - pub fn event_ptr(&self) -> Ptr { - Ptr::from(&self.event) - } - - /// Returns the components that triggered the observer, out of the - /// components defined in `B`. Does not necessarily include all of them as - /// `B` acts like an `OR` filter rather than an `AND` filter. - pub fn components(&self) -> &[ComponentId] { - &self.trigger.components - } - - /// Returns the [`Entity`] that observed the triggered event. - /// This allows you to despawn the observer, ceasing observation. - /// - /// # Examples - /// - /// ```rust - /// # use bevy_ecs::prelude::*; - /// - /// #[derive(Event, EntityEvent)] - /// struct AssertEvent; - /// - /// fn assert_observer(trigger: On) { - /// assert_eq!(trigger.observer(), trigger.target()); - /// } - /// - /// let mut world = World::new(); - /// let observer = world.spawn(Observer::new(assert_observer)).id(); - /// - /// world.trigger_targets(AssertEvent, observer); - /// ``` - pub fn observer(&self) -> Entity { - self.trigger.observer - } - - /// Returns the source code location that triggered this observer. - pub fn caller(&self) -> MaybeLocation { - self.trigger.caller - } -} - -impl<'w, E: EntityEvent, B: Bundle> On<'w, E, B> { - /// Returns the [`Entity`] that was targeted by the `event` that triggered this observer. - /// - /// Note that if event propagation is enabled, this may not be the same as the original target of the event, - /// which can be accessed via [`On::original_target`]. - /// - /// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`]. - pub fn target(&self) -> Entity { - self.trigger.current_target.unwrap_or(Entity::PLACEHOLDER) - } - - /// Returns the original [`Entity`] that the `event` was targeted at when it was first triggered. - /// - /// If event propagation is not enabled, this will always return the same value as [`On::target`]. - /// - /// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`]. - pub fn original_target(&self) -> Entity { - self.trigger.original_target.unwrap_or(Entity::PLACEHOLDER) - } - - /// Enables or disables event propagation, allowing the same event to trigger observers on a chain of different entities. - /// - /// The path an event will propagate along is specified by its associated [`Traversal`] component. By default, events - /// use `()` which ends the path immediately and prevents propagation. - /// - /// To enable propagation, you must: - /// + Set [`EntityEvent::Traversal`] to the component you want to propagate along. - /// + Either call `propagate(true)` in the first observer or set [`EntityEvent::AUTO_PROPAGATE`] to `true`. - /// - /// You can prevent an event from propagating further using `propagate(false)`. - /// - /// [`Traversal`]: crate::traversal::Traversal - pub fn propagate(&mut self, should_propagate: bool) { - *self.propagate = should_propagate; - } - - /// Returns the value of the flag that controls event propagation. See [`propagate`] for more information. - /// - /// [`propagate`]: On::propagate - pub fn get_propagate(&self) -> bool { - *self.propagate - } -} - -impl<'w, E: Debug, B: Bundle> Debug for On<'w, E, B> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("On") - .field("event", &self.event) - .field("propagate", &self.propagate) - .field("trigger", &self.trigger) - .field("_marker", &self._marker) - .finish() - } -} - -impl<'w, E, B: Bundle> Deref for On<'w, E, B> { - type Target = E; - - fn deref(&self) -> &Self::Target { - self.event - } -} - -impl<'w, E, B: Bundle> DerefMut for On<'w, E, B> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.event - } -} - -/// Metadata about a specific [`Event`] that triggered an observer. -/// -/// This information is exposed via methods on [`On`]. -#[derive(Debug)] -pub struct ObserverTrigger { - /// The [`Entity`] of the observer handling the trigger. - pub observer: Entity, - /// The [`Event`] the trigger targeted. - pub event_type: ComponentId, - /// The [`ComponentId`]s the trigger targeted. - pub components: SmallVec<[ComponentId; 2]>, - /// The entity that the entity-event targeted, if any. - /// - /// Note that if event propagation is enabled, this may not be the same as [`ObserverTrigger::original_target`]. - pub current_target: Option, - /// The entity that the entity-event was originally targeted at, if any. - /// - /// If event propagation is enabled, this will be the first entity that the event was targeted at, - /// even if the event was propagated to other entities. - pub original_target: Option, - /// The location of the source code that triggered the observer. - pub caller: MaybeLocation, -} - -impl ObserverTrigger { - /// Returns the components that the trigger targeted. - pub fn components(&self) -> &[ComponentId] { - &self.components - } -} diff --git a/src/observer/trigger_targets.rs b/src/observer/trigger_targets.rs deleted file mode 100644 index 77728e4..0000000 --- a/src/observer/trigger_targets.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! Stores the [`TriggerTargets`] trait. - -use crate::{component::ComponentId, prelude::*}; -use alloc::vec::Vec; -use variadics_please::all_tuples; - -/// Represents a collection of targets for a specific [`On`] instance of an [`Event`]. -/// -/// When an event is triggered with [`TriggerTargets`], any [`Observer`] that watches for that specific -/// event-target combination will run. -/// -/// This trait is implemented for both [`Entity`] and [`ComponentId`], allowing you to target specific entities or components. -/// It is also implemented for various collections of these types, such as [`Vec`], arrays, and tuples, -/// allowing you to trigger events for multiple targets at once. -pub trait TriggerTargets { - /// The components the trigger should target. - fn components(&self) -> impl Iterator + Clone + '_; - - /// The entities the trigger should target. - fn entities(&self) -> impl Iterator + Clone + '_; -} - -impl TriggerTargets for &T { - fn components(&self) -> impl Iterator + Clone + '_ { - (**self).components() - } - - fn entities(&self) -> impl Iterator + Clone + '_ { - (**self).entities() - } -} - -impl TriggerTargets for Entity { - fn components(&self) -> impl Iterator + Clone + '_ { - [].into_iter() - } - - fn entities(&self) -> impl Iterator + Clone + '_ { - core::iter::once(*self) - } -} - -impl TriggerTargets for ComponentId { - fn components(&self) -> impl Iterator + Clone + '_ { - core::iter::once(*self) - } - - fn entities(&self) -> impl Iterator + Clone + '_ { - [].into_iter() - } -} - -impl TriggerTargets for Vec { - fn components(&self) -> impl Iterator + Clone + '_ { - self.iter().flat_map(T::components) - } - - fn entities(&self) -> impl Iterator + Clone + '_ { - self.iter().flat_map(T::entities) - } -} - -impl TriggerTargets for [T; N] { - fn components(&self) -> impl Iterator + Clone + '_ { - self.iter().flat_map(T::components) - } - - fn entities(&self) -> impl Iterator + Clone + '_ { - self.iter().flat_map(T::entities) - } -} - -impl TriggerTargets for [T] { - fn components(&self) -> impl Iterator + Clone + '_ { - self.iter().flat_map(T::components) - } - - fn entities(&self) -> impl Iterator + Clone + '_ { - self.iter().flat_map(T::entities) - } -} - -macro_rules! impl_trigger_targets_tuples { - ($(#[$meta:meta])* $($trigger_targets: ident),*) => { - #[expect(clippy::allow_attributes, reason = "can't guarantee violation of non_snake_case")] - #[allow(non_snake_case, reason = "`all_tuples!()` generates non-snake-case variable names.")] - $(#[$meta])* - impl<$($trigger_targets: TriggerTargets),*> TriggerTargets for ($($trigger_targets,)*) - { - fn components(&self) -> impl Iterator + Clone + '_ { - let iter = [].into_iter(); - let ($($trigger_targets,)*) = self; - $( - let iter = iter.chain($trigger_targets.components()); - )* - iter - } - - fn entities(&self) -> impl Iterator + Clone + '_ { - let iter = [].into_iter(); - let ($($trigger_targets,)*) = self; - $( - let iter = iter.chain($trigger_targets.entities()); - )* - iter - } - } - } -} - -all_tuples!( - #[doc(fake_variadic)] - impl_trigger_targets_tuples, - 0, - 15, - T -); diff --git a/src/observers.rs b/src/observers.rs index e0b97c5..211300f 100644 --- a/src/observers.rs +++ b/src/observers.rs @@ -30,13 +30,16 @@ pub type ObserverFn = fn(event: &ComponentEvent, world: &SimpleWorld, env: &Env) /// trigger immediately on component changes rather than polling. /// /// # Example -/// ```ignore -/// fn on_position_added(event: &ComponentEvent, world: &SimpleWorld, env: &Env) { -/// // React to a position component being added -/// } +/// ``` +/// use cougr_core::observers::{ComponentEvent, ObserverRegistry}; +/// use cougr_core::simple_world::SimpleWorld; +/// use soroban_sdk::{symbol_short, Env}; +/// +/// fn on_position_added(_event: &ComponentEvent, _world: &SimpleWorld, _env: &Env) {} /// /// let mut registry = ObserverRegistry::new(); /// registry.on_add(symbol_short!("pos"), on_position_added); +/// assert_eq!(registry.observer_count(), 1); /// ``` pub struct ObserverRegistry { observers: Vec<(Symbol, ComponentEventKind, ObserverFn)>, @@ -89,12 +92,20 @@ impl Default for ObserverRegistry { /// allowing them to see the updated state. /// /// # Example -/// ```ignore +/// ``` +/// use cougr_core::observers::{ComponentEvent, ObservedWorld}; +/// use cougr_core::simple_world::SimpleWorld; +/// use soroban_sdk::{symbol_short, Bytes, Env}; +/// +/// fn my_observer(_event: &ComponentEvent, _world: &SimpleWorld, _env: &Env) {} +/// /// let env = Env::default(); /// let world = SimpleWorld::new(&env); /// let mut observed = ObservedWorld::new(world); +/// let entity_id = observed.spawn_entity(); /// observed.observers_mut().on_add(symbol_short!("pos"), my_observer); -/// observed.add_component(entity_id, symbol_short!("pos"), data, &env); +/// observed.add_component(entity_id, symbol_short!("pos"), Bytes::new(&env), &env); +/// assert!(observed.world().has_component(entity_id, &symbol_short!("pos"))); /// ``` pub struct ObservedWorld { world: SimpleWorld, diff --git a/src/plugin.rs b/src/plugin.rs index 30053a4..77f2b95 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -10,7 +10,12 @@ use soroban_sdk::{Env, Symbol}; /// Each plugin gets access to a `PluginApp` builder during `build()`. /// /// # Example -/// ```ignore +/// ```no_run +/// # use cougr_core::plugin::{Plugin, PluginApp}; +/// # use cougr_core::simple_world::SimpleWorld; +/// # use soroban_sdk::Env; +/// # fn gravity_system(_world: &mut SimpleWorld, _env: &Env) {} +/// # fn collision_system(_world: &mut SimpleWorld, _env: &Env) {} /// struct PhysicsPlugin; /// /// impl Plugin for PhysicsPlugin { @@ -33,13 +38,29 @@ pub trait Plugin { /// and `HookRegistry` for modular game configuration. /// /// # Example -/// ```ignore +/// ```no_run +/// # use cougr_core::plugin::{Plugin, PluginApp}; +/// # use cougr_core::simple_world::SimpleWorld; +/// # use soroban_sdk::Env; +/// # struct PhysicsPlugin; +/// # struct ScoringPlugin; +/// # fn physics_system(_world: &mut SimpleWorld, _env: &Env) {} +/// # fn scoring_system(_world: &mut SimpleWorld, _env: &Env) {} +/// # impl Plugin for PhysicsPlugin { +/// # fn name(&self) -> &'static str { "physics" } +/// # fn build(&self, app: &mut PluginApp) { app.add_system("physics", physics_system); } +/// # } +/// # impl Plugin for ScoringPlugin { +/// # fn name(&self) -> &'static str { "scoring" } +/// # fn build(&self, app: &mut PluginApp) { app.add_system("scoring", scoring_system); } +/// # } /// let env = Env::default(); /// let mut app = PluginApp::new(&env); /// app.add_plugin(PhysicsPlugin); /// app.add_plugin(ScoringPlugin); /// app.run(&env); /// let world = app.into_world(); +/// assert_eq!(world.version(), 0); /// ``` pub struct PluginApp { world: SimpleWorld, diff --git a/src/query.rs b/src/query.rs index a5776cd..10f499f 100644 --- a/src/query.rs +++ b/src/query.rs @@ -132,11 +132,21 @@ impl QueryState { /// Automatically invalidates when the world version changes. /// /// # Example -/// ```ignore -/// let mut cache = SimpleQueryCache::new(symbol_short!("position")); +/// ``` +/// use cougr_core::query::SimpleQueryCache; +/// use cougr_core::simple_world::SimpleWorld; +/// use soroban_sdk::{symbol_short, Bytes, Env}; +/// +/// let env = Env::default(); +/// let mut world = SimpleWorld::new(&env); +/// let entity = world.spawn_entity(); +/// world.add_component(entity, symbol_short!("position"), Bytes::new(&env)); +/// +/// let mut cache = SimpleQueryCache::new(symbol_short!("position"), &env); /// let entities = cache.execute(&world, &env); -/// // Second call returns cached results if world hasn't changed +/// assert_eq!(entities.len(), 1); /// let entities2 = cache.execute(&world, &env); +/// assert_eq!(entities2.len(), 1); /// ``` pub struct SimpleQueryCache { component_type: Symbol, diff --git a/src/query/access.rs b/src/query/access.rs deleted file mode 100644 index 0c5b29f..0000000 --- a/src/query/access.rs +++ /dev/null @@ -1,1896 +0,0 @@ -use crate::component::ComponentId; -use crate::storage::SparseSetIndex; -use crate::world::World; -use alloc::{format, string::String, vec, vec::Vec}; -use core::{fmt, fmt::Debug, marker::PhantomData}; -use derive_more::From; -use fixedbitset::FixedBitSet; -use thiserror::Error; - -/// A wrapper struct to make Debug representations of [`FixedBitSet`] easier -/// to read, when used to store [`SparseSetIndex`]. -/// -/// Instead of the raw integer representation of the `FixedBitSet`, the list of -/// `T` valid for [`SparseSetIndex`] is shown. -/// -/// Normal `FixedBitSet` `Debug` output: -/// ```text -/// read_and_writes: FixedBitSet { data: [ 160 ], length: 8 } -/// ``` -/// -/// Which, unless you are a computer, doesn't help much understand what's in -/// the set. With `FormattedBitSet`, we convert the present set entries into -/// what they stand for, it is much clearer what is going on: -/// ```text -/// read_and_writes: [ ComponentId(5), ComponentId(7) ] -/// ``` -struct FormattedBitSet<'a, T: SparseSetIndex> { - bit_set: &'a FixedBitSet, - _marker: PhantomData, -} - -impl<'a, T: SparseSetIndex> FormattedBitSet<'a, T> { - fn new(bit_set: &'a FixedBitSet) -> Self { - Self { - bit_set, - _marker: PhantomData, - } - } -} - -impl<'a, T: SparseSetIndex + Debug> Debug for FormattedBitSet<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list() - .entries(self.bit_set.ones().map(T::get_sparse_set_index)) - .finish() - } -} - -/// Tracks read and write access to specific elements in a collection. -/// -/// Used internally to ensure soundness during system initialization and execution. -/// See the [`is_compatible`](Access::is_compatible) and [`get_conflicts`](Access::get_conflicts) functions. -#[derive(Eq, PartialEq)] -pub struct Access { - /// All accessed components, or forbidden components if - /// `Self::component_read_and_writes_inverted` is set. - component_read_and_writes: FixedBitSet, - /// All exclusively-accessed components, or components that may not be - /// exclusively accessed if `Self::component_writes_inverted` is set. - component_writes: FixedBitSet, - /// All accessed resources. - resource_read_and_writes: FixedBitSet, - /// The exclusively-accessed resources. - resource_writes: FixedBitSet, - /// Is `true` if this component can read all components *except* those - /// present in `Self::component_read_and_writes`. - component_read_and_writes_inverted: bool, - /// Is `true` if this component can write to all components *except* those - /// present in `Self::component_writes`. - component_writes_inverted: bool, - /// Is `true` if this has access to all resources. - /// This field is a performance optimization for `&World` (also harder to mess up for soundness). - reads_all_resources: bool, - /// Is `true` if this has mutable access to all resources. - /// If this is true, then `reads_all` must also be true. - writes_all_resources: bool, - // Components that are not accessed, but whose presence in an archetype affect query results. - archetypal: FixedBitSet, - marker: PhantomData, -} - -// This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. -impl Clone for Access { - fn clone(&self) -> Self { - Self { - component_read_and_writes: self.component_read_and_writes.clone(), - component_writes: self.component_writes.clone(), - resource_read_and_writes: self.resource_read_and_writes.clone(), - resource_writes: self.resource_writes.clone(), - component_read_and_writes_inverted: self.component_read_and_writes_inverted, - component_writes_inverted: self.component_writes_inverted, - reads_all_resources: self.reads_all_resources, - writes_all_resources: self.writes_all_resources, - archetypal: self.archetypal.clone(), - marker: PhantomData, - } - } - - fn clone_from(&mut self, source: &Self) { - self.component_read_and_writes - .clone_from(&source.component_read_and_writes); - self.component_writes.clone_from(&source.component_writes); - self.resource_read_and_writes - .clone_from(&source.resource_read_and_writes); - self.resource_writes.clone_from(&source.resource_writes); - self.component_read_and_writes_inverted = source.component_read_and_writes_inverted; - self.component_writes_inverted = source.component_writes_inverted; - self.reads_all_resources = source.reads_all_resources; - self.writes_all_resources = source.writes_all_resources; - self.archetypal.clone_from(&source.archetypal); - } -} - -impl Debug for Access { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Access") - .field( - "component_read_and_writes", - &FormattedBitSet::::new(&self.component_read_and_writes), - ) - .field( - "component_writes", - &FormattedBitSet::::new(&self.component_writes), - ) - .field( - "resource_read_and_writes", - &FormattedBitSet::::new(&self.resource_read_and_writes), - ) - .field( - "resource_writes", - &FormattedBitSet::::new(&self.resource_writes), - ) - .field( - "component_read_and_writes_inverted", - &self.component_read_and_writes_inverted, - ) - .field("component_writes_inverted", &self.component_writes_inverted) - .field("reads_all_resources", &self.reads_all_resources) - .field("writes_all_resources", &self.writes_all_resources) - .field("archetypal", &FormattedBitSet::::new(&self.archetypal)) - .finish() - } -} - -impl Default for Access { - fn default() -> Self { - Self::new() - } -} - -impl Access { - /// Creates an empty [`Access`] collection. - pub const fn new() -> Self { - Self { - reads_all_resources: false, - writes_all_resources: false, - component_read_and_writes_inverted: false, - component_writes_inverted: false, - component_read_and_writes: FixedBitSet::new(), - component_writes: FixedBitSet::new(), - resource_read_and_writes: FixedBitSet::new(), - resource_writes: FixedBitSet::new(), - archetypal: FixedBitSet::new(), - marker: PhantomData, - } - } - - fn add_component_sparse_set_index_read(&mut self, index: usize) { - if !self.component_read_and_writes_inverted { - self.component_read_and_writes.grow_and_insert(index); - } else if index < self.component_read_and_writes.len() { - self.component_read_and_writes.remove(index); - } - } - - fn add_component_sparse_set_index_write(&mut self, index: usize) { - if !self.component_writes_inverted { - self.component_writes.grow_and_insert(index); - } else if index < self.component_writes.len() { - self.component_writes.remove(index); - } - } - - /// Adds access to the component given by `index`. - pub fn add_component_read(&mut self, index: T) { - let sparse_set_index = index.sparse_set_index(); - self.add_component_sparse_set_index_read(sparse_set_index); - } - - /// Adds exclusive access to the component given by `index`. - pub fn add_component_write(&mut self, index: T) { - let sparse_set_index = index.sparse_set_index(); - self.add_component_sparse_set_index_read(sparse_set_index); - self.add_component_sparse_set_index_write(sparse_set_index); - } - - /// Adds access to the resource given by `index`. - pub fn add_resource_read(&mut self, index: T) { - self.resource_read_and_writes - .grow_and_insert(index.sparse_set_index()); - } - - /// Adds exclusive access to the resource given by `index`. - pub fn add_resource_write(&mut self, index: T) { - self.resource_read_and_writes - .grow_and_insert(index.sparse_set_index()); - self.resource_writes - .grow_and_insert(index.sparse_set_index()); - } - - fn remove_component_sparse_set_index_read(&mut self, index: usize) { - if self.component_read_and_writes_inverted { - self.component_read_and_writes.grow_and_insert(index); - } else if index < self.component_read_and_writes.len() { - self.component_read_and_writes.remove(index); - } - } - - fn remove_component_sparse_set_index_write(&mut self, index: usize) { - if self.component_writes_inverted { - self.component_writes.grow_and_insert(index); - } else if index < self.component_writes.len() { - self.component_writes.remove(index); - } - } - - /// Removes both read and write access to the component given by `index`. - /// - /// Because this method corresponds to the set difference operator ∖, it can - /// create complicated logical formulas that you should verify correctness - /// of. For example, A ∪ (B ∖ A) isn't equivalent to (A ∪ B) ∖ A, so you - /// can't replace a call to `remove_component_read` followed by a call to - /// `extend` with a call to `extend` followed by a call to - /// `remove_component_read`. - pub fn remove_component_read(&mut self, index: T) { - let sparse_set_index = index.sparse_set_index(); - self.remove_component_sparse_set_index_write(sparse_set_index); - self.remove_component_sparse_set_index_read(sparse_set_index); - } - - /// Removes write access to the component given by `index`. - /// - /// Because this method corresponds to the set difference operator ∖, it can - /// create complicated logical formulas that you should verify correctness - /// of. For example, A ∪ (B ∖ A) isn't equivalent to (A ∪ B) ∖ A, so you - /// can't replace a call to `remove_component_write` followed by a call to - /// `extend` with a call to `extend` followed by a call to - /// `remove_component_write`. - pub fn remove_component_write(&mut self, index: T) { - let sparse_set_index = index.sparse_set_index(); - self.remove_component_sparse_set_index_write(sparse_set_index); - } - - /// Adds an archetypal (indirect) access to the component given by `index`. - /// - /// This is for components whose values are not accessed (and thus will never cause conflicts), - /// but whose presence in an archetype may affect query results. - /// - /// Currently, this is only used for [`Has`] and [`Allows`]. - /// - /// [`Has`]: crate::query::Has - /// [`Allows`]: crate::query::filter::Allows - pub fn add_archetypal(&mut self, index: T) { - self.archetypal.grow_and_insert(index.sparse_set_index()); - } - - /// Returns `true` if this can access the component given by `index`. - pub fn has_component_read(&self, index: T) -> bool { - self.component_read_and_writes_inverted - ^ self - .component_read_and_writes - .contains(index.sparse_set_index()) - } - - /// Returns `true` if this can access any component. - pub fn has_any_component_read(&self) -> bool { - self.component_read_and_writes_inverted || !self.component_read_and_writes.is_clear() - } - - /// Returns `true` if this can exclusively access the component given by `index`. - pub fn has_component_write(&self, index: T) -> bool { - self.component_writes_inverted ^ self.component_writes.contains(index.sparse_set_index()) - } - - /// Returns `true` if this accesses any component mutably. - pub fn has_any_component_write(&self) -> bool { - self.component_writes_inverted || !self.component_writes.is_clear() - } - - /// Returns `true` if this can access the resource given by `index`. - pub fn has_resource_read(&self, index: T) -> bool { - self.reads_all_resources - || self - .resource_read_and_writes - .contains(index.sparse_set_index()) - } - - /// Returns `true` if this can access any resource. - pub fn has_any_resource_read(&self) -> bool { - self.reads_all_resources || !self.resource_read_and_writes.is_clear() - } - - /// Returns `true` if this can exclusively access the resource given by `index`. - pub fn has_resource_write(&self, index: T) -> bool { - self.writes_all_resources || self.resource_writes.contains(index.sparse_set_index()) - } - - /// Returns `true` if this accesses any resource mutably. - pub fn has_any_resource_write(&self) -> bool { - self.writes_all_resources || !self.resource_writes.is_clear() - } - - /// Returns `true` if this accesses any resources or components. - pub fn has_any_read(&self) -> bool { - self.has_any_component_read() || self.has_any_resource_read() - } - - /// Returns `true` if this accesses any resources or components mutably. - pub fn has_any_write(&self) -> bool { - self.has_any_component_write() || self.has_any_resource_write() - } - - /// Returns true if this has an archetypal (indirect) access to the component given by `index`. - /// - /// This is a component whose value is not accessed (and thus will never cause conflicts), - /// but whose presence in an archetype may affect query results. - /// - /// Currently, this is only used for [`Has`]. - /// - /// [`Has`]: crate::query::Has - pub fn has_archetypal(&self, index: T) -> bool { - self.archetypal.contains(index.sparse_set_index()) - } - - /// Sets this as having access to all components (i.e. `EntityRef`). - #[inline] - pub fn read_all_components(&mut self) { - self.component_read_and_writes_inverted = true; - self.component_read_and_writes.clear(); - } - - /// Sets this as having mutable access to all components (i.e. `EntityMut`). - #[inline] - pub fn write_all_components(&mut self) { - self.read_all_components(); - self.component_writes_inverted = true; - self.component_writes.clear(); - } - - /// Sets this as having access to all resources (i.e. `&World`). - #[inline] - pub const fn read_all_resources(&mut self) { - self.reads_all_resources = true; - } - - /// Sets this as having mutable access to all resources (i.e. `&mut World`). - #[inline] - pub const fn write_all_resources(&mut self) { - self.reads_all_resources = true; - self.writes_all_resources = true; - } - - /// Sets this as having access to all indexed elements (i.e. `&World`). - #[inline] - pub fn read_all(&mut self) { - self.read_all_components(); - self.read_all_resources(); - } - - /// Sets this as having mutable access to all indexed elements (i.e. `&mut World`). - #[inline] - pub fn write_all(&mut self) { - self.write_all_components(); - self.write_all_resources(); - } - - /// Returns `true` if this has access to all components (i.e. `EntityRef`). - #[inline] - pub fn has_read_all_components(&self) -> bool { - self.component_read_and_writes_inverted && self.component_read_and_writes.is_clear() - } - - /// Returns `true` if this has write access to all components (i.e. `EntityMut`). - #[inline] - pub fn has_write_all_components(&self) -> bool { - self.component_writes_inverted && self.component_writes.is_clear() - } - - /// Returns `true` if this has access to all resources (i.e. `EntityRef`). - #[inline] - pub fn has_read_all_resources(&self) -> bool { - self.reads_all_resources - } - - /// Returns `true` if this has write access to all resources (i.e. `EntityMut`). - #[inline] - pub fn has_write_all_resources(&self) -> bool { - self.writes_all_resources - } - - /// Returns `true` if this has access to all indexed elements (i.e. `&World`). - pub fn has_read_all(&self) -> bool { - self.has_read_all_components() && self.has_read_all_resources() - } - - /// Returns `true` if this has write access to all indexed elements (i.e. `&mut World`). - pub fn has_write_all(&self) -> bool { - self.has_write_all_components() && self.has_write_all_resources() - } - - /// Removes all writes. - pub fn clear_writes(&mut self) { - self.writes_all_resources = false; - self.component_writes_inverted = false; - self.component_writes.clear(); - self.resource_writes.clear(); - } - - /// Removes all accesses. - pub fn clear(&mut self) { - self.reads_all_resources = false; - self.writes_all_resources = false; - self.component_read_and_writes_inverted = false; - self.component_writes_inverted = false; - self.component_read_and_writes.clear(); - self.component_writes.clear(); - self.resource_read_and_writes.clear(); - self.resource_writes.clear(); - } - - /// Adds all access from `other`. - pub fn extend(&mut self, other: &Access) { - invertible_union_with( - &mut self.component_read_and_writes, - &mut self.component_read_and_writes_inverted, - &other.component_read_and_writes, - other.component_read_and_writes_inverted, - ); - invertible_union_with( - &mut self.component_writes, - &mut self.component_writes_inverted, - &other.component_writes, - other.component_writes_inverted, - ); - - self.reads_all_resources = self.reads_all_resources || other.reads_all_resources; - self.writes_all_resources = self.writes_all_resources || other.writes_all_resources; - self.resource_read_and_writes - .union_with(&other.resource_read_and_writes); - self.resource_writes.union_with(&other.resource_writes); - self.archetypal.union_with(&other.archetypal); - } - - /// Removes any access from `self` that would conflict with `other`. - /// This removes any reads and writes for any component written by `other`, - /// and removes any writes for any component read by `other`. - pub fn remove_conflicting_access(&mut self, other: &Access) { - invertible_difference_with( - &mut self.component_read_and_writes, - &mut self.component_read_and_writes_inverted, - &other.component_writes, - other.component_writes_inverted, - ); - invertible_difference_with( - &mut self.component_writes, - &mut self.component_writes_inverted, - &other.component_read_and_writes, - other.component_read_and_writes_inverted, - ); - - if other.reads_all_resources { - self.writes_all_resources = false; - self.resource_writes.clear(); - } - if other.writes_all_resources { - self.reads_all_resources = false; - self.resource_read_and_writes.clear(); - } - self.resource_read_and_writes - .difference_with(&other.resource_writes); - self.resource_writes - .difference_with(&other.resource_read_and_writes); - } - - /// Returns `true` if the access and `other` can be active at the same time, - /// only looking at their component access. - /// - /// [`Access`] instances are incompatible if one can write - /// an element that the other can read or write. - pub fn is_components_compatible(&self, other: &Access) -> bool { - // We have a conflict if we write and they read or write, or if they - // write and we read or write. - for ( - lhs_writes, - rhs_reads_and_writes, - lhs_writes_inverted, - rhs_reads_and_writes_inverted, - ) in [ - ( - &self.component_writes, - &other.component_read_and_writes, - self.component_writes_inverted, - other.component_read_and_writes_inverted, - ), - ( - &other.component_writes, - &self.component_read_and_writes, - other.component_writes_inverted, - self.component_read_and_writes_inverted, - ), - ] { - match (lhs_writes_inverted, rhs_reads_and_writes_inverted) { - (true, true) => return false, - (false, true) => { - if !lhs_writes.is_subset(rhs_reads_and_writes) { - return false; - } - } - (true, false) => { - if !rhs_reads_and_writes.is_subset(lhs_writes) { - return false; - } - } - (false, false) => { - if !lhs_writes.is_disjoint(rhs_reads_and_writes) { - return false; - } - } - } - } - - true - } - - /// Returns `true` if the access and `other` can be active at the same time, - /// only looking at their resource access. - /// - /// [`Access`] instances are incompatible if one can write - /// an element that the other can read or write. - pub fn is_resources_compatible(&self, other: &Access) -> bool { - if self.writes_all_resources { - return !other.has_any_resource_read(); - } - - if other.writes_all_resources { - return !self.has_any_resource_read(); - } - - if self.reads_all_resources { - return !other.has_any_resource_write(); - } - - if other.reads_all_resources { - return !self.has_any_resource_write(); - } - - self.resource_writes - .is_disjoint(&other.resource_read_and_writes) - && other - .resource_writes - .is_disjoint(&self.resource_read_and_writes) - } - - /// Returns `true` if the access and `other` can be active at the same time. - /// - /// [`Access`] instances are incompatible if one can write - /// an element that the other can read or write. - pub fn is_compatible(&self, other: &Access) -> bool { - self.is_components_compatible(other) && self.is_resources_compatible(other) - } - - /// Returns `true` if the set's component access is a subset of another, i.e. `other`'s component access - /// contains at least all the values in `self`. - pub fn is_subset_components(&self, other: &Access) -> bool { - for ( - our_components, - their_components, - our_components_inverted, - their_components_inverted, - ) in [ - ( - &self.component_read_and_writes, - &other.component_read_and_writes, - self.component_read_and_writes_inverted, - other.component_read_and_writes_inverted, - ), - ( - &self.component_writes, - &other.component_writes, - self.component_writes_inverted, - other.component_writes_inverted, - ), - ] { - match (our_components_inverted, their_components_inverted) { - (true, true) => { - if !their_components.is_subset(our_components) { - return false; - } - } - (true, false) => { - return false; - } - (false, true) => { - if !our_components.is_disjoint(their_components) { - return false; - } - } - (false, false) => { - if !our_components.is_subset(their_components) { - return false; - } - } - } - } - - true - } - - /// Returns `true` if the set's resource access is a subset of another, i.e. `other`'s resource access - /// contains at least all the values in `self`. - pub fn is_subset_resources(&self, other: &Access) -> bool { - if self.writes_all_resources { - return other.writes_all_resources; - } - - if other.writes_all_resources { - return true; - } - - if self.reads_all_resources { - return other.reads_all_resources; - } - - if other.reads_all_resources { - return self.resource_writes.is_subset(&other.resource_writes); - } - - self.resource_read_and_writes - .is_subset(&other.resource_read_and_writes) - && self.resource_writes.is_subset(&other.resource_writes) - } - - /// Returns `true` if the set is a subset of another, i.e. `other` contains - /// at least all the values in `self`. - pub fn is_subset(&self, other: &Access) -> bool { - self.is_subset_components(other) && self.is_subset_resources(other) - } - - fn get_component_conflicts(&self, other: &Access) -> AccessConflicts { - let mut conflicts = FixedBitSet::new(); - - // We have a conflict if we write and they read or write, or if they - // write and we read or write. - for ( - lhs_writes, - rhs_reads_and_writes, - lhs_writes_inverted, - rhs_reads_and_writes_inverted, - ) in [ - ( - &self.component_writes, - &other.component_read_and_writes, - self.component_writes_inverted, - other.component_read_and_writes_inverted, - ), - ( - &other.component_writes, - &self.component_read_and_writes, - other.component_writes_inverted, - self.component_read_and_writes_inverted, - ), - ] { - // There's no way that I can see to do this without a temporary. - // Neither CNF nor DNF allows us to avoid one. - let temp_conflicts: FixedBitSet = - match (lhs_writes_inverted, rhs_reads_and_writes_inverted) { - (true, true) => return AccessConflicts::All, - (false, true) => lhs_writes.difference(rhs_reads_and_writes).collect(), - (true, false) => rhs_reads_and_writes.difference(lhs_writes).collect(), - (false, false) => lhs_writes.intersection(rhs_reads_and_writes).collect(), - }; - conflicts.union_with(&temp_conflicts); - } - - AccessConflicts::Individual(conflicts) - } - - /// Returns a vector of elements that the access and `other` cannot access at the same time. - pub fn get_conflicts(&self, other: &Access) -> AccessConflicts { - let mut conflicts = match self.get_component_conflicts(other) { - AccessConflicts::All => return AccessConflicts::All, - AccessConflicts::Individual(conflicts) => conflicts, - }; - - if self.reads_all_resources { - if other.writes_all_resources { - return AccessConflicts::All; - } - conflicts.extend(other.resource_writes.ones()); - } - - if other.reads_all_resources { - if self.writes_all_resources { - return AccessConflicts::All; - } - conflicts.extend(self.resource_writes.ones()); - } - if self.writes_all_resources { - conflicts.extend(other.resource_read_and_writes.ones()); - } - - if other.writes_all_resources { - conflicts.extend(self.resource_read_and_writes.ones()); - } - - conflicts.extend( - self.resource_writes - .intersection(&other.resource_read_and_writes), - ); - conflicts.extend( - self.resource_read_and_writes - .intersection(&other.resource_writes), - ); - AccessConflicts::Individual(conflicts) - } - - /// Returns the indices of the resources this has access to. - pub fn resource_reads_and_writes(&self) -> impl Iterator + '_ { - self.resource_read_and_writes - .ones() - .map(T::get_sparse_set_index) - } - - /// Returns the indices of the resources this has non-exclusive access to. - pub fn resource_reads(&self) -> impl Iterator + '_ { - self.resource_read_and_writes - .difference(&self.resource_writes) - .map(T::get_sparse_set_index) - } - - /// Returns the indices of the resources this has exclusive access to. - pub fn resource_writes(&self) -> impl Iterator + '_ { - self.resource_writes.ones().map(T::get_sparse_set_index) - } - - /// Returns the indices of the components that this has an archetypal access to. - /// - /// These are components whose values are not accessed (and thus will never cause conflicts), - /// but whose presence in an archetype may affect query results. - /// - /// Currently, this is only used for [`Has`]. - /// - /// [`Has`]: crate::query::Has - pub fn archetypal(&self) -> impl Iterator + '_ { - self.archetypal.ones().map(T::get_sparse_set_index) - } - - /// Returns an iterator over the component IDs and their [`ComponentAccessKind`]. - /// - /// Returns `Err(UnboundedAccess)` if the access is unbounded. - /// This typically occurs when an [`Access`] is marked as accessing all - /// components, and then adding exceptions. - /// - /// # Examples - /// - /// ```rust - /// # use bevy_ecs::query::{Access, ComponentAccessKind}; - /// let mut access = Access::::default(); - /// - /// access.add_component_read(1); - /// access.add_component_write(2); - /// access.add_archetypal(3); - /// - /// let result = access - /// .try_iter_component_access() - /// .map(Iterator::collect::>); - /// - /// assert_eq!( - /// result, - /// Ok(vec![ - /// ComponentAccessKind::Shared(1), - /// ComponentAccessKind::Exclusive(2), - /// ComponentAccessKind::Archetypal(3), - /// ]), - /// ); - /// ``` - pub fn try_iter_component_access( - &self, - ) -> Result> + '_, UnboundedAccessError> { - // component_writes_inverted is only ever true when component_read_and_writes_inverted is - // also true. Therefore it is sufficient to check just component_read_and_writes_inverted. - if self.component_read_and_writes_inverted { - return Err(UnboundedAccessError { - writes_inverted: self.component_writes_inverted, - read_and_writes_inverted: self.component_read_and_writes_inverted, - }); - } - - let reads_and_writes = self.component_read_and_writes.ones().map(|index| { - let sparse_index = T::get_sparse_set_index(index); - - if self.component_writes.contains(index) { - ComponentAccessKind::Exclusive(sparse_index) - } else { - ComponentAccessKind::Shared(sparse_index) - } - }); - - let archetypal = self - .archetypal - .ones() - .filter(|&index| { - !self.component_writes.contains(index) - && !self.component_read_and_writes.contains(index) - }) - .map(|index| ComponentAccessKind::Archetypal(T::get_sparse_set_index(index))); - - Ok(reads_and_writes.chain(archetypal)) - } -} - -/// Performs an in-place union of `other` into `self`, where either set may be inverted. -/// -/// Each set corresponds to a `FixedBitSet` if `inverted` is `false`, -/// or to the infinite (co-finite) complement of the `FixedBitSet` if `inverted` is `true`. -/// -/// This updates the `self` set to include any elements in the `other` set. -/// Note that this may change `self_inverted` to `true` if we add an infinite -/// set to a finite one, resulting in a new infinite set. -fn invertible_union_with( - self_set: &mut FixedBitSet, - self_inverted: &mut bool, - other_set: &FixedBitSet, - other_inverted: bool, -) { - match (*self_inverted, other_inverted) { - (true, true) => self_set.intersect_with(other_set), - (true, false) => self_set.difference_with(other_set), - (false, true) => { - *self_inverted = true; - // We have to grow here because the new bits are going to get flipped to 1. - self_set.grow(other_set.len()); - self_set.toggle_range(..); - self_set.intersect_with(other_set); - } - (false, false) => self_set.union_with(other_set), - } -} - -/// Performs an in-place set difference of `other` from `self`, where either set may be inverted. -/// -/// Each set corresponds to a `FixedBitSet` if `inverted` is `false`, -/// or to the infinite (co-finite) complement of the `FixedBitSet` if `inverted` is `true`. -/// -/// This updates the `self` set to remove any elements in the `other` set. -/// Note that this may change `self_inverted` to `false` if we remove an -/// infinite set from another infinite one, resulting in a finite difference. -fn invertible_difference_with( - self_set: &mut FixedBitSet, - self_inverted: &mut bool, - other_set: &FixedBitSet, - other_inverted: bool, -) { - // We can share the implementation of `invertible_union_with` with some algebra: - // A - B = A & !B = !(!A | B) - *self_inverted = !*self_inverted; - invertible_union_with(self_set, self_inverted, other_set, other_inverted); - *self_inverted = !*self_inverted; -} - -/// Error returned when attempting to iterate over items included in an [`Access`] -/// if the access excludes items rather than including them. -#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)] -#[error("Access is unbounded")] -pub struct UnboundedAccessError { - /// [`Access`] is defined in terms of _excluding_ [exclusive](ComponentAccessKind::Exclusive) - /// access. - pub writes_inverted: bool, - /// [`Access`] is defined in terms of _excluding_ [shared](ComponentAccessKind::Shared) and - /// [exclusive](ComponentAccessKind::Exclusive) access. - pub read_and_writes_inverted: bool, -} - -/// Describes the level of access for a particular component as defined in an [`Access`]. -#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] -pub enum ComponentAccessKind { - /// Archetypical access, such as `Has`. - Archetypal(T), - /// Shared access, such as `&Foo`. - Shared(T), - /// Exclusive access, such as `&mut Foo`. - Exclusive(T), -} - -impl ComponentAccessKind { - /// Gets the index of this `ComponentAccessKind`. - pub fn index(&self) -> &T { - let (Self::Archetypal(value) | Self::Shared(value) | Self::Exclusive(value)) = self; - value - } -} - -/// An [`Access`] that has been filtered to include and exclude certain combinations of elements. -/// -/// Used internally to statically check if queries are disjoint. -/// -/// Subtle: a `read` or `write` in `access` should not be considered to imply a -/// `with` access. -/// -/// For example consider `Query>` this only has a `read` of `T` as doing -/// otherwise would allow for queries to be considered disjoint when they shouldn't: -/// - `Query<(&mut T, Option<&U>)>` read/write `T`, read `U`, with `U` -/// - `Query<&mut T, Without>` read/write `T`, without `U` -/// from this we could reasonably conclude that the queries are disjoint but they aren't. -/// -/// In order to solve this the actual access that `Query<(&mut T, Option<&U>)>` has -/// is read/write `T`, read `U`. It must still have a read `U` access otherwise the following -/// queries would be incorrectly considered disjoint: -/// - `Query<&mut T>` read/write `T` -/// - `Query>` accesses nothing -/// -/// See comments the [`WorldQuery`](super::WorldQuery) impls of [`AnyOf`](super::AnyOf)/`Option`/[`Or`](super::Or) for more information. -#[derive(Debug, Eq, PartialEq)] -pub struct FilteredAccess { - pub(crate) access: Access, - pub(crate) required: FixedBitSet, - // An array of filter sets to express `With` or `Without` clauses in disjunctive normal form, for example: `Or<(With, With)>`. - // Filters like `(With, Or<(With, Without)>` are expanded into `Or<((With, With), (With, Without))>`. - pub(crate) filter_sets: Vec>, -} - -// This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. -impl Clone for FilteredAccess { - fn clone(&self) -> Self { - Self { - access: self.access.clone(), - required: self.required.clone(), - filter_sets: self.filter_sets.clone(), - } - } - - fn clone_from(&mut self, source: &Self) { - self.access.clone_from(&source.access); - self.required.clone_from(&source.required); - self.filter_sets.clone_from(&source.filter_sets); - } -} - -impl Default for FilteredAccess { - fn default() -> Self { - Self::matches_everything() - } -} - -impl From> for FilteredAccessSet { - fn from(filtered_access: FilteredAccess) -> Self { - let mut base = FilteredAccessSet::::default(); - base.add(filtered_access); - base - } -} - -/// Records how two accesses conflict with each other -#[derive(Debug, PartialEq, From)] -pub enum AccessConflicts { - /// Conflict is for all indices - All, - /// There is a conflict for a subset of indices - Individual(FixedBitSet), -} - -impl AccessConflicts { - fn add(&mut self, other: &Self) { - match (self, other) { - (s, AccessConflicts::All) => { - *s = AccessConflicts::All; - } - (AccessConflicts::Individual(this), AccessConflicts::Individual(other)) => { - this.extend(other.ones()); - } - _ => {} - } - } - - /// Returns true if there are no conflicts present - pub fn is_empty(&self) -> bool { - match self { - Self::All => false, - Self::Individual(set) => set.is_empty(), - } - } - - pub(crate) fn format_conflict_list(&self, world: &World) -> String { - match self { - AccessConflicts::All => String::new(), - AccessConflicts::Individual(indices) => indices - .ones() - .map(|index| { - format!( - "{}", - world - .components - .get_name(ComponentId::get_sparse_set_index(index)) - .unwrap() - .shortname() - ) - }) - .collect::>() - .join(", "), - } - } - - /// An [`AccessConflicts`] which represents the absence of any conflict - pub(crate) fn empty() -> Self { - Self::Individual(FixedBitSet::new()) - } -} - -impl From> for AccessConflicts { - fn from(value: Vec) -> Self { - Self::Individual(value.iter().map(T::sparse_set_index).collect()) - } -} - -impl FilteredAccess { - /// Returns a `FilteredAccess` which has no access and matches everything. - /// This is the equivalent of a `TRUE` logic atom. - pub fn matches_everything() -> Self { - Self { - access: Access::default(), - required: FixedBitSet::default(), - filter_sets: vec![AccessFilters::default()], - } - } - - /// Returns a `FilteredAccess` which has no access and matches nothing. - /// This is the equivalent of a `FALSE` logic atom. - pub fn matches_nothing() -> Self { - Self { - access: Access::default(), - required: FixedBitSet::default(), - filter_sets: Vec::new(), - } - } - - /// Returns a reference to the underlying unfiltered access. - #[inline] - pub fn access(&self) -> &Access { - &self.access - } - - /// Returns a mutable reference to the underlying unfiltered access. - #[inline] - pub fn access_mut(&mut self) -> &mut Access { - &mut self.access - } - - /// Adds access to the component given by `index`. - pub fn add_component_read(&mut self, index: T) { - self.access.add_component_read(index.clone()); - self.add_required(index.clone()); - self.and_with(index); - } - - /// Adds exclusive access to the component given by `index`. - pub fn add_component_write(&mut self, index: T) { - self.access.add_component_write(index.clone()); - self.add_required(index.clone()); - self.and_with(index); - } - - /// Adds access to the resource given by `index`. - pub fn add_resource_read(&mut self, index: T) { - self.access.add_resource_read(index.clone()); - } - - /// Adds exclusive access to the resource given by `index`. - pub fn add_resource_write(&mut self, index: T) { - self.access.add_resource_write(index.clone()); - } - - fn add_required(&mut self, index: T) { - self.required.grow_and_insert(index.sparse_set_index()); - } - - /// Adds a `With` filter: corresponds to a conjunction (AND) operation. - /// - /// Suppose we begin with `Or<(With, With)>`, which is represented by an array of two `AccessFilter` instances. - /// Adding `AND With` via this method transforms it into the equivalent of `Or<((With, With), (With, With))>`. - pub fn and_with(&mut self, index: T) { - for filter in &mut self.filter_sets { - filter.with.grow_and_insert(index.sparse_set_index()); - } - } - - /// Adds a `Without` filter: corresponds to a conjunction (AND) operation. - /// - /// Suppose we begin with `Or<(With, With)>`, which is represented by an array of two `AccessFilter` instances. - /// Adding `AND Without` via this method transforms it into the equivalent of `Or<((With, Without), (With, Without))>`. - pub fn and_without(&mut self, index: T) { - for filter in &mut self.filter_sets { - filter.without.grow_and_insert(index.sparse_set_index()); - } - } - - /// Appends an array of filters: corresponds to a disjunction (OR) operation. - /// - /// As the underlying array of filters represents a disjunction, - /// where each element (`AccessFilters`) represents a conjunction, - /// we can simply append to the array. - pub fn append_or(&mut self, other: &FilteredAccess) { - self.filter_sets.append(&mut other.filter_sets.clone()); - } - - /// Adds all of the accesses from `other` to `self`. - pub fn extend_access(&mut self, other: &FilteredAccess) { - self.access.extend(&other.access); - } - - /// Returns `true` if this and `other` can be active at the same time. - pub fn is_compatible(&self, other: &FilteredAccess) -> bool { - // Resources are read from the world rather than the filtered archetypes, - // so they must be compatible even if the filters are disjoint. - if !self.access.is_resources_compatible(&other.access) { - return false; - } - - if self.access.is_components_compatible(&other.access) { - return true; - } - - // If the access instances are incompatible, we want to check that whether filters can - // guarantee that queries are disjoint. - // Since the `filter_sets` array represents a Disjunctive Normal Form formula ("ORs of ANDs"), - // we need to make sure that each filter set (ANDs) rule out every filter set from the `other` instance. - // - // For example, `Query<&mut C, Or<(With, Without)>>` is compatible `Query<&mut C, (With, Without)>`, - // but `Query<&mut C, Or<(Without, Without)>>` isn't compatible with `Query<&mut C, Or<(With, With)>>`. - self.filter_sets.iter().all(|filter| { - other - .filter_sets - .iter() - .all(|other_filter| filter.is_ruled_out_by(other_filter)) - }) - } - - /// Returns a vector of elements that this and `other` cannot access at the same time. - pub fn get_conflicts(&self, other: &FilteredAccess) -> AccessConflicts { - if !self.is_compatible(other) { - // filters are disjoint, so we can just look at the unfiltered intersection - return self.access.get_conflicts(&other.access); - } - AccessConflicts::empty() - } - - /// Adds all access and filters from `other`. - /// - /// Corresponds to a conjunction operation (AND) for filters. - /// - /// Extending `Or<(With, Without)>` with `Or<(With, Without)>` will result in - /// `Or<((With, With), (With, Without), (Without, With), (Without, Without))>`. - pub fn extend(&mut self, other: &FilteredAccess) { - self.access.extend(&other.access); - self.required.union_with(&other.required); - - // We can avoid allocating a new array of bitsets if `other` contains just a single set of filters: - // in this case we can short-circuit by performing an in-place union for each bitset. - if other.filter_sets.len() == 1 { - for filter in &mut self.filter_sets { - filter.with.union_with(&other.filter_sets[0].with); - filter.without.union_with(&other.filter_sets[0].without); - } - return; - } - - let mut new_filters = Vec::with_capacity(self.filter_sets.len() * other.filter_sets.len()); - for filter in &self.filter_sets { - for other_filter in &other.filter_sets { - let mut new_filter = filter.clone(); - new_filter.with.union_with(&other_filter.with); - new_filter.without.union_with(&other_filter.without); - new_filters.push(new_filter); - } - } - self.filter_sets = new_filters; - } - - /// Sets the underlying unfiltered access as having access to all indexed elements. - pub fn read_all(&mut self) { - self.access.read_all(); - } - - /// Sets the underlying unfiltered access as having mutable access to all indexed elements. - pub fn write_all(&mut self) { - self.access.write_all(); - } - - /// Sets the underlying unfiltered access as having access to all components. - pub fn read_all_components(&mut self) { - self.access.read_all_components(); - } - - /// Sets the underlying unfiltered access as having mutable access to all components. - pub fn write_all_components(&mut self) { - self.access.write_all_components(); - } - - /// Returns `true` if the set is a subset of another, i.e. `other` contains - /// at least all the values in `self`. - pub fn is_subset(&self, other: &FilteredAccess) -> bool { - self.required.is_subset(&other.required) && self.access().is_subset(other.access()) - } - - /// Returns the indices of the elements that this access filters for. - pub fn with_filters(&self) -> impl Iterator + '_ { - self.filter_sets - .iter() - .flat_map(|f| f.with.ones().map(T::get_sparse_set_index)) - } - - /// Returns the indices of the elements that this access filters out. - pub fn without_filters(&self) -> impl Iterator + '_ { - self.filter_sets - .iter() - .flat_map(|f| f.without.ones().map(T::get_sparse_set_index)) - } - - /// Returns true if the index is used by this `FilteredAccess` in any way - pub fn contains(&self, index: T) -> bool { - self.access().has_component_read(index.clone()) - || self.access().has_archetypal(index.clone()) - || self.filter_sets.iter().any(|f| { - f.with.contains(index.sparse_set_index()) - || f.without.contains(index.sparse_set_index()) - }) - } -} - -#[derive(Eq, PartialEq)] -pub(crate) struct AccessFilters { - pub(crate) with: FixedBitSet, - pub(crate) without: FixedBitSet, - _index_type: PhantomData, -} - -// This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. -impl Clone for AccessFilters { - fn clone(&self) -> Self { - Self { - with: self.with.clone(), - without: self.without.clone(), - _index_type: PhantomData, - } - } - - fn clone_from(&mut self, source: &Self) { - self.with.clone_from(&source.with); - self.without.clone_from(&source.without); - } -} - -impl Debug for AccessFilters { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AccessFilters") - .field("with", &FormattedBitSet::::new(&self.with)) - .field("without", &FormattedBitSet::::new(&self.without)) - .finish() - } -} - -impl Default for AccessFilters { - fn default() -> Self { - Self { - with: FixedBitSet::default(), - without: FixedBitSet::default(), - _index_type: PhantomData, - } - } -} - -impl AccessFilters { - fn is_ruled_out_by(&self, other: &Self) -> bool { - // Although not technically complete, we don't consider the case when `AccessFilters`'s - // `without` bitset contradicts its own `with` bitset (e.g. `(With, Without)`). - // Such query would be considered compatible with any other query, but as it's almost - // always an error, we ignore this case instead of treating such query as compatible - // with others. - !self.with.is_disjoint(&other.without) || !self.without.is_disjoint(&other.with) - } -} - -/// A collection of [`FilteredAccess`] instances. -/// -/// Used internally to statically check if systems have conflicting access. -/// -/// It stores multiple sets of accesses. -/// - A "combined" set, which is the access of all filters in this set combined. -/// - The set of access of each individual filters in this set. -#[derive(Debug, PartialEq, Eq)] -pub struct FilteredAccessSet { - combined_access: Access, - filtered_accesses: Vec>, -} - -// This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. -impl Clone for FilteredAccessSet { - fn clone(&self) -> Self { - Self { - combined_access: self.combined_access.clone(), - filtered_accesses: self.filtered_accesses.clone(), - } - } - - fn clone_from(&mut self, source: &Self) { - self.combined_access.clone_from(&source.combined_access); - self.filtered_accesses.clone_from(&source.filtered_accesses); - } -} - -impl FilteredAccessSet { - /// Creates an empty [`FilteredAccessSet`]. - pub const fn new() -> Self { - Self { - combined_access: Access::new(), - filtered_accesses: Vec::new(), - } - } - - /// Returns a reference to the unfiltered access of the entire set. - #[inline] - pub fn combined_access(&self) -> &Access { - &self.combined_access - } - - /// Returns `true` if this and `other` can be active at the same time. - /// - /// Access conflict resolution happen in two steps: - /// 1. A "coarse" check, if there is no mutual unfiltered conflict between - /// `self` and `other`, we already know that the two access sets are - /// compatible. - /// 2. A "fine grained" check, it kicks in when the "coarse" check fails. - /// the two access sets might still be compatible if some of the accesses - /// are restricted with the [`With`](super::With) or [`Without`](super::Without) filters so that access is - /// mutually exclusive. The fine grained phase iterates over all filters in - /// the `self` set and compares it to all the filters in the `other` set, - /// making sure they are all mutually compatible. - pub fn is_compatible(&self, other: &FilteredAccessSet) -> bool { - if self.combined_access.is_compatible(other.combined_access()) { - return true; - } - for filtered in &self.filtered_accesses { - for other_filtered in &other.filtered_accesses { - if !filtered.is_compatible(other_filtered) { - return false; - } - } - } - true - } - - /// Returns a vector of elements that this set and `other` cannot access at the same time. - pub fn get_conflicts(&self, other: &FilteredAccessSet) -> AccessConflicts { - // if the unfiltered access is incompatible, must check each pair - let mut conflicts = AccessConflicts::empty(); - if !self.combined_access.is_compatible(other.combined_access()) { - for filtered in &self.filtered_accesses { - for other_filtered in &other.filtered_accesses { - conflicts.add(&filtered.get_conflicts(other_filtered)); - } - } - } - conflicts - } - - /// Returns a vector of elements that this set and `other` cannot access at the same time. - pub fn get_conflicts_single(&self, filtered_access: &FilteredAccess) -> AccessConflicts { - // if the unfiltered access is incompatible, must check each pair - let mut conflicts = AccessConflicts::empty(); - if !self.combined_access.is_compatible(filtered_access.access()) { - for filtered in &self.filtered_accesses { - conflicts.add(&filtered.get_conflicts(filtered_access)); - } - } - conflicts - } - - /// Adds the filtered access to the set. - pub fn add(&mut self, filtered_access: FilteredAccess) { - self.combined_access.extend(&filtered_access.access); - self.filtered_accesses.push(filtered_access); - } - - /// Adds a read access to a resource to the set. - pub fn add_unfiltered_resource_read(&mut self, index: T) { - let mut filter = FilteredAccess::default(); - filter.add_resource_read(index); - self.add(filter); - } - - /// Adds a write access to a resource to the set. - pub fn add_unfiltered_resource_write(&mut self, index: T) { - let mut filter = FilteredAccess::default(); - filter.add_resource_write(index); - self.add(filter); - } - - /// Adds read access to all resources to the set. - pub fn add_unfiltered_read_all_resources(&mut self) { - let mut filter = FilteredAccess::default(); - filter.access.read_all_resources(); - self.add(filter); - } - - /// Adds write access to all resources to the set. - pub fn add_unfiltered_write_all_resources(&mut self) { - let mut filter = FilteredAccess::default(); - filter.access.write_all_resources(); - self.add(filter); - } - - /// Adds all of the accesses from the passed set to `self`. - pub fn extend(&mut self, filtered_access_set: FilteredAccessSet) { - self.combined_access - .extend(&filtered_access_set.combined_access); - self.filtered_accesses - .extend(filtered_access_set.filtered_accesses); - } - - /// Marks the set as reading all possible indices of type T. - pub fn read_all(&mut self) { - let mut filter = FilteredAccess::matches_everything(); - filter.read_all(); - self.add(filter); - } - - /// Marks the set as writing all T. - pub fn write_all(&mut self) { - let mut filter = FilteredAccess::matches_everything(); - filter.write_all(); - self.add(filter); - } - - /// Removes all accesses stored in this set. - pub fn clear(&mut self) { - self.combined_access.clear(); - self.filtered_accesses.clear(); - } -} - -impl Default for FilteredAccessSet { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::{invertible_difference_with, invertible_union_with}; - use crate::query::{ - access::AccessFilters, Access, AccessConflicts, ComponentAccessKind, FilteredAccess, - FilteredAccessSet, UnboundedAccessError, - }; - use alloc::{vec, vec::Vec}; - use core::marker::PhantomData; - use fixedbitset::FixedBitSet; - - fn create_sample_access() -> Access { - let mut access = Access::::default(); - - access.add_component_read(1); - access.add_component_read(2); - access.add_component_write(3); - access.add_archetypal(5); - access.read_all(); - - access - } - - fn create_sample_filtered_access() -> FilteredAccess { - let mut filtered_access = FilteredAccess::::default(); - - filtered_access.add_component_write(1); - filtered_access.add_component_read(2); - filtered_access.add_required(3); - filtered_access.and_with(4); - - filtered_access - } - - fn create_sample_access_filters() -> AccessFilters { - let mut access_filters = AccessFilters::::default(); - - access_filters.with.grow_and_insert(3); - access_filters.without.grow_and_insert(5); - - access_filters - } - - fn create_sample_filtered_access_set() -> FilteredAccessSet { - let mut filtered_access_set = FilteredAccessSet::::default(); - - filtered_access_set.add_unfiltered_resource_read(2); - filtered_access_set.add_unfiltered_resource_write(4); - filtered_access_set.read_all(); - - filtered_access_set - } - - #[test] - fn test_access_clone() { - let original: Access = create_sample_access(); - let cloned = original.clone(); - - assert_eq!(original, cloned); - } - - #[test] - fn test_access_clone_from() { - let original: Access = create_sample_access(); - let mut cloned = Access::::default(); - - cloned.add_component_write(7); - cloned.add_component_read(4); - cloned.add_archetypal(8); - cloned.write_all(); - - cloned.clone_from(&original); - - assert_eq!(original, cloned); - } - - #[test] - fn test_filtered_access_clone() { - let original: FilteredAccess = create_sample_filtered_access(); - let cloned = original.clone(); - - assert_eq!(original, cloned); - } - - #[test] - fn test_filtered_access_clone_from() { - let original: FilteredAccess = create_sample_filtered_access(); - let mut cloned = FilteredAccess::::default(); - - cloned.add_component_write(7); - cloned.add_component_read(4); - cloned.append_or(&FilteredAccess::default()); - - cloned.clone_from(&original); - - assert_eq!(original, cloned); - } - - #[test] - fn test_access_filters_clone() { - let original: AccessFilters = create_sample_access_filters(); - let cloned = original.clone(); - - assert_eq!(original, cloned); - } - - #[test] - fn test_access_filters_clone_from() { - let original: AccessFilters = create_sample_access_filters(); - let mut cloned = AccessFilters::::default(); - - cloned.with.grow_and_insert(1); - cloned.without.grow_and_insert(2); - - cloned.clone_from(&original); - - assert_eq!(original, cloned); - } - - #[test] - fn test_filtered_access_set_clone() { - let original: FilteredAccessSet = create_sample_filtered_access_set(); - let cloned = original.clone(); - - assert_eq!(original, cloned); - } - - #[test] - fn test_filtered_access_set_from() { - let original: FilteredAccessSet = create_sample_filtered_access_set(); - let mut cloned = FilteredAccessSet::::default(); - - cloned.add_unfiltered_resource_read(7); - cloned.add_unfiltered_resource_write(9); - cloned.write_all(); - - cloned.clone_from(&original); - - assert_eq!(original, cloned); - } - - #[test] - fn read_all_access_conflicts() { - // read_all / single write - let mut access_a = Access::::default(); - access_a.add_component_write(0); - - let mut access_b = Access::::default(); - access_b.read_all(); - - assert!(!access_b.is_compatible(&access_a)); - - // read_all / read_all - let mut access_a = Access::::default(); - access_a.read_all(); - - let mut access_b = Access::::default(); - access_b.read_all(); - - assert!(access_b.is_compatible(&access_a)); - } - - #[test] - fn access_get_conflicts() { - let mut access_a = Access::::default(); - access_a.add_component_read(0); - access_a.add_component_read(1); - - let mut access_b = Access::::default(); - access_b.add_component_read(0); - access_b.add_component_write(1); - - assert_eq!(access_a.get_conflicts(&access_b), vec![1_usize].into()); - - let mut access_c = Access::::default(); - access_c.add_component_write(0); - access_c.add_component_write(1); - - assert_eq!( - access_a.get_conflicts(&access_c), - vec![0_usize, 1_usize].into() - ); - assert_eq!( - access_b.get_conflicts(&access_c), - vec![0_usize, 1_usize].into() - ); - - let mut access_d = Access::::default(); - access_d.add_component_read(0); - - assert_eq!(access_d.get_conflicts(&access_a), AccessConflicts::empty()); - assert_eq!(access_d.get_conflicts(&access_b), AccessConflicts::empty()); - assert_eq!(access_d.get_conflicts(&access_c), vec![0_usize].into()); - } - - #[test] - fn filtered_combined_access() { - let mut access_a = FilteredAccessSet::::default(); - access_a.add_unfiltered_resource_read(1); - - let mut filter_b = FilteredAccess::::default(); - filter_b.add_resource_write(1); - - let conflicts = access_a.get_conflicts_single(&filter_b); - assert_eq!( - &conflicts, - &AccessConflicts::from(vec![1_usize]), - "access_a: {access_a:?}, filter_b: {filter_b:?}" - ); - } - - #[test] - fn filtered_access_extend() { - let mut access_a = FilteredAccess::::default(); - access_a.add_component_read(0); - access_a.add_component_read(1); - access_a.and_with(2); - - let mut access_b = FilteredAccess::::default(); - access_b.add_component_read(0); - access_b.add_component_write(3); - access_b.and_without(4); - - access_a.extend(&access_b); - - let mut expected = FilteredAccess::::default(); - expected.add_component_read(0); - expected.add_component_read(1); - expected.and_with(2); - expected.add_component_write(3); - expected.and_without(4); - - assert!(access_a.eq(&expected)); - } - - #[test] - fn filtered_access_extend_or() { - let mut access_a = FilteredAccess::::default(); - // Exclusive access to `(&mut A, &mut B)`. - access_a.add_component_write(0); - access_a.add_component_write(1); - - // Filter by `With`. - let mut access_b = FilteredAccess::::default(); - access_b.and_with(2); - - // Filter by `(With, Without)`. - let mut access_c = FilteredAccess::::default(); - access_c.and_with(3); - access_c.and_without(4); - - // Turns `access_b` into `Or<(With, (With, Without))>`. - access_b.append_or(&access_c); - // Applies the filters to the initial query, which corresponds to the FilteredAccess' - // representation of `Query<(&mut A, &mut B), Or<(With, (With, Without))>>`. - access_a.extend(&access_b); - - // Construct the expected `FilteredAccess` struct. - // The intention here is to test that exclusive access implied by `add_write` - // forms correct normalized access structs when extended with `Or` filters. - let mut expected = FilteredAccess::::default(); - expected.add_component_write(0); - expected.add_component_write(1); - // The resulted access is expected to represent `Or<((With, With, With), (With, With, With, Without))>`. - expected.filter_sets = vec![ - AccessFilters { - with: FixedBitSet::with_capacity_and_blocks(3, [0b111]), - without: FixedBitSet::default(), - _index_type: PhantomData, - }, - AccessFilters { - with: FixedBitSet::with_capacity_and_blocks(4, [0b1011]), - without: FixedBitSet::with_capacity_and_blocks(5, [0b10000]), - _index_type: PhantomData, - }, - ]; - - assert_eq!(access_a, expected); - } - - #[test] - fn try_iter_component_access_simple() { - let mut access = Access::::default(); - - access.add_component_read(1); - access.add_component_read(2); - access.add_component_write(3); - access.add_archetypal(5); - - let result = access - .try_iter_component_access() - .map(Iterator::collect::>); - - assert_eq!( - result, - Ok(vec![ - ComponentAccessKind::Shared(1), - ComponentAccessKind::Shared(2), - ComponentAccessKind::Exclusive(3), - ComponentAccessKind::Archetypal(5), - ]), - ); - } - - #[test] - fn try_iter_component_access_unbounded_write_all() { - let mut access = Access::::default(); - - access.add_component_read(1); - access.add_component_read(2); - access.write_all(); - - let result = access - .try_iter_component_access() - .map(Iterator::collect::>); - - assert_eq!( - result, - Err(UnboundedAccessError { - writes_inverted: true, - read_and_writes_inverted: true - }), - ); - } - - #[test] - fn try_iter_component_access_unbounded_read_all() { - let mut access = Access::::default(); - - access.add_component_read(1); - access.add_component_read(2); - access.read_all(); - - let result = access - .try_iter_component_access() - .map(Iterator::collect::>); - - assert_eq!( - result, - Err(UnboundedAccessError { - writes_inverted: false, - read_and_writes_inverted: true - }), - ); - } - - /// Create a `FixedBitSet` with a given number of total bits and a given list of bits to set. - /// Setting the number of bits is important in tests since the `PartialEq` impl checks that the length matches. - fn bit_set(bits: usize, iter: impl IntoIterator) -> FixedBitSet { - let mut result = FixedBitSet::with_capacity(bits); - result.extend(iter); - result - } - - #[test] - fn invertible_union_with_tests() { - let invertible_union = |mut self_inverted: bool, other_inverted: bool| { - // Check all four possible bit states: In both sets, the first, the second, or neither - let mut self_set = bit_set(4, [0, 1]); - let other_set = bit_set(4, [0, 2]); - invertible_union_with( - &mut self_set, - &mut self_inverted, - &other_set, - other_inverted, - ); - (self_set, self_inverted) - }; - - // Check each combination of `inverted` flags - let (s, i) = invertible_union(false, false); - // [0, 1] | [0, 2] = [0, 1, 2] - assert_eq!((s, i), (bit_set(4, [0, 1, 2]), false)); - - let (s, i) = invertible_union(false, true); - // [0, 1] | [1, 3, ...] = [0, 1, 3, ...] - assert_eq!((s, i), (bit_set(4, [2]), true)); - - let (s, i) = invertible_union(true, false); - // [2, 3, ...] | [0, 2] = [0, 2, 3, ...] - assert_eq!((s, i), (bit_set(4, [1]), true)); - - let (s, i) = invertible_union(true, true); - // [2, 3, ...] | [1, 3, ...] = [1, 2, 3, ...] - assert_eq!((s, i), (bit_set(4, [0]), true)); - } - - #[test] - fn invertible_union_with_different_lengths() { - // When adding a large inverted set to a small normal set, - // make sure we invert the bits beyond the original length. - // Failing to call `grow` before `toggle_range` would cause bit 1 to be zero, - // which would incorrectly treat it as included in the output set. - let mut self_set = bit_set(1, [0]); - let mut self_inverted = false; - let other_set = bit_set(3, [0, 1]); - let other_inverted = true; - invertible_union_with( - &mut self_set, - &mut self_inverted, - &other_set, - other_inverted, - ); - - // [0] | [2, ...] = [0, 2, ...] - assert_eq!((self_set, self_inverted), (bit_set(3, [1]), true)); - } - - #[test] - fn invertible_difference_with_tests() { - let invertible_difference = |mut self_inverted: bool, other_inverted: bool| { - // Check all four possible bit states: In both sets, the first, the second, or neither - let mut self_set = bit_set(4, [0, 1]); - let other_set = bit_set(4, [0, 2]); - invertible_difference_with( - &mut self_set, - &mut self_inverted, - &other_set, - other_inverted, - ); - (self_set, self_inverted) - }; - - // Check each combination of `inverted` flags - let (s, i) = invertible_difference(false, false); - // [0, 1] - [0, 2] = [1] - assert_eq!((s, i), (bit_set(4, [1]), false)); - - let (s, i) = invertible_difference(false, true); - // [0, 1] - [1, 3, ...] = [0] - assert_eq!((s, i), (bit_set(4, [0]), false)); - - let (s, i) = invertible_difference(true, false); - // [2, 3, ...] - [0, 2] = [3, ...] - assert_eq!((s, i), (bit_set(4, [0, 1, 2]), true)); - - let (s, i) = invertible_difference(true, true); - // [2, 3, ...] - [1, 3, ...] = [2] - assert_eq!((s, i), (bit_set(4, [2]), false)); - } -} diff --git a/src/query/builder.rs b/src/query/builder.rs deleted file mode 100644 index b545caa..0000000 --- a/src/query/builder.rs +++ /dev/null @@ -1,531 +0,0 @@ -use core::marker::PhantomData; - -use crate::{ - component::{ComponentId, StorageType}, - prelude::*, -}; - -use super::{FilteredAccess, QueryData, QueryFilter}; - -/// Builder struct to create [`QueryState`] instances at runtime. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct A; -/// # -/// # #[derive(Component)] -/// # struct B; -/// # -/// # #[derive(Component)] -/// # struct C; -/// # -/// let mut world = World::new(); -/// let entity_a = world.spawn((A, B)).id(); -/// let entity_b = world.spawn((A, C)).id(); -/// -/// // Instantiate the builder using the type signature of the iterator you will consume -/// let mut query = QueryBuilder::<(Entity, &B)>::new(&mut world) -/// // Add additional terms through builder methods -/// .with::() -/// .without::() -/// .build(); -/// -/// // Consume the QueryState -/// let (entity, b) = query.single(&world).unwrap(); -/// ``` -pub struct QueryBuilder<'w, D: QueryData = (), F: QueryFilter = ()> { - access: FilteredAccess, - world: &'w mut World, - or: bool, - first: bool, - _marker: PhantomData<(D, F)>, -} - -impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { - /// Creates a new builder with the accesses required for `Q` and `F` - pub fn new(world: &'w mut World) -> Self { - let fetch_state = D::init_state(world); - let filter_state = F::init_state(world); - - let mut access = FilteredAccess::default(); - D::update_component_access(&fetch_state, &mut access); - - // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the - // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch - // because they are evaluated *before* a specific reference is constructed. - let mut filter_access = FilteredAccess::default(); - F::update_component_access(&filter_state, &mut filter_access); - - // Merge the temporary filter access with the main access. This ensures that filter access is - // properly considered in a global "cross-query" context (both within systems and across systems). - access.extend(&filter_access); - - Self { - access, - world, - or: false, - first: false, - _marker: PhantomData, - } - } - - pub(super) fn is_dense(&self) -> bool { - // Note: `component_id` comes from the user in safe code, so we cannot trust it to - // exist. If it doesn't exist we pessimistically assume it's sparse. - let is_dense = |component_id| { - self.world() - .components() - .get_info(component_id) - .is_some_and(|info| info.storage_type() == StorageType::Table) - }; - - let Ok(component_accesses) = self.access.access().try_iter_component_access() else { - // Access is unbounded, pessimistically assume it's sparse. - return false; - }; - - component_accesses - .map(|access| *access.index()) - .all(is_dense) - && !self.access.access().has_read_all_components() - && self.access.with_filters().all(is_dense) - && self.access.without_filters().all(is_dense) - } - - /// Returns a reference to the world passed to [`Self::new`]. - pub fn world(&self) -> &World { - self.world - } - - /// Returns a mutable reference to the world passed to [`Self::new`]. - pub fn world_mut(&mut self) -> &mut World { - self.world - } - - /// Adds access to self's underlying [`FilteredAccess`] respecting [`Self::or`] and [`Self::and`] - pub fn extend_access(&mut self, mut access: FilteredAccess) { - if self.or { - if self.first { - access.required.clear(); - self.access.extend(&access); - self.first = false; - } else { - self.access.append_or(&access); - } - } else { - self.access.extend(&access); - } - } - - /// Adds accesses required for `T` to self. - pub fn data(&mut self) -> &mut Self { - let state = T::init_state(self.world); - let mut access = FilteredAccess::default(); - T::update_component_access(&state, &mut access); - self.extend_access(access); - self - } - - /// Adds filter from `T` to self. - pub fn filter(&mut self) -> &mut Self { - let state = T::init_state(self.world); - let mut access = FilteredAccess::default(); - T::update_component_access(&state, &mut access); - self.extend_access(access); - self - } - - /// Adds [`With`] to the [`FilteredAccess`] of self. - pub fn with(&mut self) -> &mut Self { - self.filter::>(); - self - } - - /// Adds [`With`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. - pub fn with_id(&mut self, id: ComponentId) -> &mut Self { - let mut access = FilteredAccess::default(); - access.and_with(id); - self.extend_access(access); - self - } - - /// Adds [`Without`] to the [`FilteredAccess`] of self. - pub fn without(&mut self) -> &mut Self { - self.filter::>(); - self - } - - /// Adds [`Without`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. - pub fn without_id(&mut self, id: ComponentId) -> &mut Self { - let mut access = FilteredAccess::default(); - access.and_without(id); - self.extend_access(access); - self - } - - /// Adds `&T` to the [`FilteredAccess`] of self. - pub fn ref_id(&mut self, id: ComponentId) -> &mut Self { - self.with_id(id); - self.access.add_component_read(id); - self - } - - /// Adds `&mut T` to the [`FilteredAccess`] of self. - pub fn mut_id(&mut self, id: ComponentId) -> &mut Self { - self.with_id(id); - self.access.add_component_write(id); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder and then adds all accesses from that builder to self as optional. - pub fn optional(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - f(&mut builder); - self.access.extend_access(builder.access()); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder and then adds all accesses from that builder to self. - /// - /// Primarily used when inside a [`Self::or`] closure to group several terms. - pub fn and(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - f(&mut builder); - let access = builder.access().clone(); - self.extend_access(access); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder, all accesses added to that builder will become terms in an or expression. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct A; - /// # - /// # #[derive(Component)] - /// # struct B; - /// # - /// # let mut world = World::new(); - /// # - /// QueryBuilder::::new(&mut world).or(|builder| { - /// builder.with::(); - /// builder.with::(); - /// }); - /// // is equivalent to - /// QueryBuilder::::new(&mut world).filter::, With)>>(); - /// ``` - pub fn or(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - builder.or = true; - builder.first = true; - f(&mut builder); - self.access.extend(builder.access()); - self - } - - /// Returns a reference to the [`FilteredAccess`] that will be provided to the built [`Query`]. - pub fn access(&self) -> &FilteredAccess { - &self.access - } - - /// Transmute the existing builder adding required accesses. - /// This will maintain all existing accesses. - /// - /// If including a filter type see [`Self::transmute_filtered`] - pub fn transmute(&mut self) -> &mut QueryBuilder<'w, NewD> { - self.transmute_filtered::() - } - - /// Transmute the existing builder adding required accesses. - /// This will maintain all existing accesses. - pub fn transmute_filtered( - &mut self, - ) -> &mut QueryBuilder<'w, NewD, NewF> { - let fetch_state = NewD::init_state(self.world); - let filter_state = NewF::init_state(self.world); - - let mut access = FilteredAccess::default(); - NewD::update_component_access(&fetch_state, &mut access); - NewF::update_component_access(&filter_state, &mut access); - - self.extend_access(access); - // SAFETY: - // - We have included all required accesses for NewQ and NewF - // - The layout of all QueryBuilder instances is the same - unsafe { core::mem::transmute(self) } - } - - /// Create a [`QueryState`] with the accesses of the builder. - /// - /// Takes `&mut self` to access the inner world reference while initializing - /// state for the new [`QueryState`] - pub fn build(&mut self) -> QueryState { - QueryState::::from_builder(self) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - prelude::*, - world::{EntityMutExcept, EntityRefExcept, FilteredEntityMut, FilteredEntityRef}, - }; - use std::dbg; - - #[derive(Component, PartialEq, Debug)] - struct A(usize); - - #[derive(Component, PartialEq, Debug)] - struct B(usize); - - #[derive(Component, PartialEq, Debug)] - struct C(usize); - - #[test] - fn builder_with_without_static() { - let mut world = World::new(); - let entity_a = world.spawn((A(0), B(0))).id(); - let entity_b = world.spawn((A(0), C(0))).id(); - - let mut query_a = QueryBuilder::::new(&mut world) - .with::() - .without::() - .build(); - assert_eq!(entity_a, query_a.single(&world).unwrap()); - - let mut query_b = QueryBuilder::::new(&mut world) - .with::() - .without::() - .build(); - assert_eq!(entity_b, query_b.single(&world).unwrap()); - } - - #[test] - fn builder_with_without_dynamic() { - let mut world = World::new(); - let entity_a = world.spawn((A(0), B(0))).id(); - let entity_b = world.spawn((A(0), C(0))).id(); - let component_id_a = world.register_component::(); - let component_id_b = world.register_component::(); - let component_id_c = world.register_component::(); - - let mut query_a = QueryBuilder::::new(&mut world) - .with_id(component_id_a) - .without_id(component_id_c) - .build(); - assert_eq!(entity_a, query_a.single(&world).unwrap()); - - let mut query_b = QueryBuilder::::new(&mut world) - .with_id(component_id_a) - .without_id(component_id_b) - .build(); - assert_eq!(entity_b, query_b.single(&world).unwrap()); - } - - #[test] - fn builder_or() { - let mut world = World::new(); - world.spawn((A(0), B(0))); - world.spawn(B(0)); - world.spawn(C(0)); - - let mut query_a = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.with::(); - }) - .build(); - assert_eq!(2, query_a.iter(&world).count()); - - let mut query_b = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.without::(); - }) - .build(); - dbg!(&query_b.component_access); - assert_eq!(2, query_b.iter(&world).count()); - - let mut query_c = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.with::(); - builder.with::(); - }) - .build(); - assert_eq!(3, query_c.iter(&world).count()); - } - - #[test] - fn builder_transmute() { - let mut world = World::new(); - world.spawn(A(0)); - world.spawn((A(1), B(0))); - let mut query = QueryBuilder::<()>::new(&mut world) - .with::() - .transmute::<&A>() - .build(); - - query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); - } - - #[test] - fn builder_static_components() { - let mut world = World::new(); - let entity = world.spawn((A(0), B(1))).id(); - - let mut query = QueryBuilder::::new(&mut world) - .data::<&A>() - .data::<&B>() - .build(); - - let entity_ref = query.single(&world).unwrap(); - - assert_eq!(entity, entity_ref.id()); - - let a = entity_ref.get::().unwrap(); - let b = entity_ref.get::().unwrap(); - - assert_eq!(0, a.0); - assert_eq!(1, b.0); - } - - #[test] - fn builder_dynamic_components() { - let mut world = World::new(); - let entity = world.spawn((A(0), B(1))).id(); - let component_id_a = world.register_component::(); - let component_id_b = world.register_component::(); - - let mut query = QueryBuilder::::new(&mut world) - .ref_id(component_id_a) - .ref_id(component_id_b) - .build(); - - let entity_ref = query.single(&world).unwrap(); - - assert_eq!(entity, entity_ref.id()); - - let a = entity_ref.get_by_id(component_id_a).unwrap(); - let b = entity_ref.get_by_id(component_id_b).unwrap(); - - // SAFETY: We set these pointers to point to these components - unsafe { - assert_eq!(0, a.deref::().0); - assert_eq!(1, b.deref::().0); - } - } - - #[test] - fn builder_provide_access() { - let mut world = World::new(); - world.spawn((A(0), B(1))); - - let mut query = - QueryBuilder::<(Entity, FilteredEntityRef, FilteredEntityMut)>::new(&mut world) - .data::<&mut A>() - .data::<&B>() - .build(); - - // The `FilteredEntityRef` only has read access, so the `FilteredEntityMut` can have read access without conflicts - let (_entity, entity_ref_1, mut entity_ref_2) = query.single_mut(&mut world).unwrap(); - assert!(entity_ref_1.get::().is_some()); - assert!(entity_ref_1.get::().is_some()); - assert!(entity_ref_2.get::().is_some()); - assert!(entity_ref_2.get_mut::().is_none()); - assert!(entity_ref_2.get::().is_some()); - assert!(entity_ref_2.get_mut::().is_none()); - - let mut query = - QueryBuilder::<(Entity, FilteredEntityMut, FilteredEntityMut)>::new(&mut world) - .data::<&mut A>() - .data::<&B>() - .build(); - - // The first `FilteredEntityMut` has write access to A, so the second one cannot have write access - let (_entity, mut entity_ref_1, mut entity_ref_2) = query.single_mut(&mut world).unwrap(); - assert!(entity_ref_1.get::().is_some()); - assert!(entity_ref_1.get_mut::().is_some()); - assert!(entity_ref_1.get::().is_some()); - assert!(entity_ref_1.get_mut::().is_none()); - assert!(entity_ref_2.get::().is_none()); - assert!(entity_ref_2.get_mut::().is_none()); - assert!(entity_ref_2.get::().is_some()); - assert!(entity_ref_2.get_mut::().is_none()); - - let mut query = QueryBuilder::<(FilteredEntityMut, &mut A, &B)>::new(&mut world) - .data::<&mut A>() - .data::<&mut B>() - .build(); - - // Any `A` access would conflict with `&mut A`, and write access to `B` would conflict with `&B`. - let (mut entity_ref, _a, _b) = query.single_mut(&mut world).unwrap(); - assert!(entity_ref.get::().is_none()); - assert!(entity_ref.get_mut::().is_none()); - assert!(entity_ref.get::().is_some()); - assert!(entity_ref.get_mut::().is_none()); - - let mut query = QueryBuilder::<(FilteredEntityMut, &mut A, &B)>::new(&mut world) - .data::() - .build(); - - // Same as above, but starting from "all" access - let (mut entity_ref, _a, _b) = query.single_mut(&mut world).unwrap(); - assert!(entity_ref.get::().is_none()); - assert!(entity_ref.get_mut::().is_none()); - assert!(entity_ref.get::().is_some()); - assert!(entity_ref.get_mut::().is_none()); - - let mut query = QueryBuilder::<(FilteredEntityMut, EntityMutExcept)>::new(&mut world) - .data::() - .build(); - - // Removing `EntityMutExcept` just leaves A - let (mut entity_ref_1, _entity_ref_2) = query.single_mut(&mut world).unwrap(); - assert!(entity_ref_1.get::().is_some()); - assert!(entity_ref_1.get_mut::().is_some()); - assert!(entity_ref_1.get::().is_none()); - assert!(entity_ref_1.get_mut::().is_none()); - - let mut query = QueryBuilder::<(FilteredEntityMut, EntityRefExcept)>::new(&mut world) - .data::() - .build(); - - // Removing `EntityRefExcept` just leaves A, plus read access - let (mut entity_ref_1, _entity_ref_2) = query.single_mut(&mut world).unwrap(); - assert!(entity_ref_1.get::().is_some()); - assert!(entity_ref_1.get_mut::().is_some()); - assert!(entity_ref_1.get::().is_some()); - assert!(entity_ref_1.get_mut::().is_none()); - } - - /// Regression test for issue #14348 - #[test] - fn builder_static_dense_dynamic_sparse() { - #[derive(Component)] - struct Dense; - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - let mut world = World::new(); - - world.spawn(Dense); - world.spawn((Dense, Sparse)); - - let mut query = QueryBuilder::<&Dense>::new(&mut world) - .with::() - .build(); - - let matched = query.iter(&world).count(); - assert_eq!(matched, 1); - } -} diff --git a/src/query/error.rs b/src/query/error.rs deleted file mode 100644 index fd431f4..0000000 --- a/src/query/error.rs +++ /dev/null @@ -1,91 +0,0 @@ -use bevy_utils::prelude::DebugName; -use thiserror::Error; - -use crate::{ - archetype::ArchetypeId, - entity::{Entity, EntityDoesNotExistError}, -}; - -/// An error that occurs when retrieving a specific [`Entity`]'s query result from [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState). -// TODO: return the type_name as part of this error -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum QueryEntityError { - /// The given [`Entity`]'s components do not match the query. - /// - /// Either it does not have a requested component, or it has a component which the query filters out. - QueryDoesNotMatch(Entity, ArchetypeId), - /// The given [`Entity`] does not exist. - EntityDoesNotExist(EntityDoesNotExistError), - /// The [`Entity`] was requested mutably more than once. - /// - /// See [`Query::get_many_mut`](crate::system::Query::get_many_mut) for an example. - AliasedMutability(Entity), -} - -impl From for QueryEntityError { - fn from(error: EntityDoesNotExistError) -> Self { - QueryEntityError::EntityDoesNotExist(error) - } -} - -impl core::error::Error for QueryEntityError {} - -impl core::fmt::Display for QueryEntityError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match *self { - Self::QueryDoesNotMatch(entity, _) => { - write!(f, "The query does not match entity {entity}") - } - Self::EntityDoesNotExist(error) => { - write!(f, "{error}") - } - Self::AliasedMutability(entity) => { - write!( - f, - "The entity with ID {entity} was requested mutably more than once" - ) - } - } - } -} - -/// An error that occurs when evaluating a [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState) as a single expected result via -/// [`single`](crate::system::Query::single) or [`single_mut`](crate::system::Query::single_mut). -#[derive(Debug, Error)] -pub enum QuerySingleError { - /// No entity fits the query. - #[error("No entities fit the query {0}")] - NoEntities(DebugName), - /// Multiple entities fit the query. - #[error("Multiple entities fit the query {0}")] - MultipleEntities(DebugName), -} - -#[cfg(test)] -mod test { - use crate::{prelude::World, query::QueryEntityError}; - use bevy_ecs_macros::Component; - - #[test] - fn query_does_not_match() { - let mut world = World::new(); - - #[derive(Component)] - struct Present1; - #[derive(Component)] - struct Present2; - #[derive(Component, Debug, PartialEq)] - struct NotPresent; - - let entity = world.spawn((Present1, Present2)); - - let (entity, archetype_id) = (entity.id(), entity.archetype().id()); - - let result = world.query::<&NotPresent>().get(&world, entity); - - assert_eq!( - result, - Err(QueryEntityError::QueryDoesNotMatch(entity, archetype_id)) - ); - } -} diff --git a/src/query/fetch.rs b/src/query/fetch.rs deleted file mode 100644 index 2564223..0000000 --- a/src/query/fetch.rs +++ /dev/null @@ -1,3254 +0,0 @@ -use crate::{ - archetype::{Archetype, Archetypes}, - bundle::Bundle, - change_detection::{MaybeLocation, Ticks, TicksMut}, - component::{Component, ComponentId, Components, Mutable, StorageType, Tick}, - entity::{Entities, Entity, EntityLocation}, - query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, - storage::{ComponentSparseSet, Table, TableRow}, - world::{ - unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, - FilteredEntityMut, FilteredEntityRef, Mut, Ref, World, - }, -}; -use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; -use bevy_utils::prelude::DebugName; -use core::{cell::UnsafeCell, marker::PhantomData, panic::Location}; -use smallvec::SmallVec; -use variadics_please::all_tuples; - -/// Types that can be fetched from a [`World`] using a [`Query`]. -/// -/// There are many types that natively implement this trait: -/// -/// - **Component references. (&T and &mut T)** -/// Fetches a component by reference (immutably or mutably). -/// - **`QueryData` tuples.** -/// If every element of a tuple implements `QueryData`, then the tuple itself also implements the same trait. -/// This enables a single `Query` to access multiple components. -/// Due to the current lack of variadic generics in Rust, the trait has been implemented for tuples from 0 to 15 elements, -/// but nesting of tuples allows infinite `WorldQuery`s. -/// - **[`Entity`].** -/// Gets the identifier of the queried entity. -/// - **[`EntityLocation`].** -/// Gets the location metadata of the queried entity. -/// - **[`SpawnDetails`].** -/// Gets the tick the entity was spawned at. -/// - **[`EntityRef`].** -/// Read-only access to arbitrary components on the queried entity. -/// - **[`EntityMut`].** -/// Mutable access to arbitrary components on the queried entity. -/// - **[`&Archetype`](Archetype).** -/// Read-only access to the archetype-level metadata of the queried entity. -/// - **[`Option`].** -/// By default, a world query only tests entities that have the matching component types. -/// Wrapping it into an `Option` will increase the query search space, and it will return `None` if an entity doesn't satisfy the `WorldQuery`. -/// - **[`AnyOf`].** -/// Equivalent to wrapping each world query inside it into an `Option`. -/// - **[`Ref`].** -/// Similar to change detection filters but it is used as a query fetch parameter. -/// It exposes methods to check for changes to the wrapped component. -/// - **[`Mut`].** -/// Mutable component access, with change detection data. -/// - **[`Has`].** -/// Returns a bool indicating whether the entity has the specified component. -/// -/// Implementing the trait manually can allow for a fundamentally new type of behavior. -/// -/// # Trait derivation -/// -/// Query design can be easily structured by deriving `QueryData` for custom types. -/// Despite the added complexity, this approach has several advantages over using `QueryData` tuples. -/// The most relevant improvements are: -/// -/// - Reusability across multiple systems. -/// - There is no need to destructure a tuple since all fields are named. -/// - Subqueries can be composed together to create a more complex query. -/// - Methods can be implemented for the query items. -/// - There is no hardcoded limit on the number of elements. -/// -/// This trait can only be derived for structs, if each field also implements `QueryData`. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// use bevy_ecs::query::QueryData; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # #[derive(Component)] -/// # struct ComponentB; -/// -/// #[derive(QueryData)] -/// struct MyQuery { -/// entity: Entity, -/// // It is required that all reference lifetimes are explicitly annotated, just like in any -/// // struct. Each lifetime should be 'static. -/// component_a: &'static ComponentA, -/// component_b: &'static ComponentB, -/// } -/// -/// fn my_system(query: Query) { -/// for q in &query { -/// q.component_a; -/// } -/// } -/// # bevy_ecs::system::assert_is_system(my_system); -/// ``` -/// -/// ## Macro expansion -/// -/// Expanding the macro will declare one or three additional structs, depending on whether or not the struct is marked as mutable. -/// For a struct named `X`, the additional structs will be: -/// -/// |Struct name|`mutable` only|Description| -/// |:---:|:---:|---| -/// |`XItem`|---|The type of the query item for `X`| -/// |`XReadOnlyItem`|✓|The type of the query item for `XReadOnly`| -/// |`XReadOnly`|✓|[`ReadOnly`] variant of `X`| -/// -/// ## Adding mutable references -/// -/// Simply adding mutable references to a derived `QueryData` will result in a compilation error: -/// -/// ```compile_fail -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::query::QueryData; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// #[derive(QueryData)] -/// struct CustomQuery { -/// component_a: &'static mut ComponentA, -/// } -/// ``` -/// -/// To grant mutable access to components, the struct must be marked with the `#[query_data(mutable)]` attribute. -/// This will also create three more structs that will be used for accessing the query immutably (see table above). -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::query::QueryData; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// #[derive(QueryData)] -/// #[query_data(mutable)] -/// struct CustomQuery { -/// component_a: &'static mut ComponentA, -/// } -/// ``` -/// -/// ## Adding methods to query items -/// -/// It is possible to add methods to query items in order to write reusable logic about related components. -/// This will often make systems more readable because low level logic is moved out from them. -/// It is done by adding `impl` blocks with methods for the `-Item` or `-ReadOnlyItem` generated structs. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::query::QueryData; -/// # -/// #[derive(Component)] -/// struct Health(f32); -/// -/// #[derive(Component)] -/// struct Buff(f32); -/// -/// #[derive(QueryData)] -/// #[query_data(mutable)] -/// struct HealthQuery { -/// health: &'static mut Health, -/// buff: Option<&'static mut Buff>, -/// } -/// -/// // `HealthQueryItem` is only available when accessing the query with mutable methods. -/// impl<'w, 's> HealthQueryItem<'w, 's> { -/// fn damage(&mut self, value: f32) { -/// self.health.0 -= value; -/// } -/// -/// fn total(&self) -> f32 { -/// self.health.0 + self.buff.as_deref().map_or(0.0, |Buff(buff)| *buff) -/// } -/// } -/// -/// // `HealthQueryReadOnlyItem` is only available when accessing the query with immutable methods. -/// impl<'w, 's> HealthQueryReadOnlyItem<'w, 's> { -/// fn total(&self) -> f32 { -/// self.health.0 + self.buff.map_or(0.0, |Buff(buff)| *buff) -/// } -/// } -/// -/// fn my_system(mut health_query: Query) { -/// // The item returned by the iterator is of type `HealthQueryReadOnlyItem`. -/// for health in health_query.iter() { -/// println!("Total: {}", health.total()); -/// } -/// // The item returned by the iterator is of type `HealthQueryItem`. -/// for mut health in &mut health_query { -/// health.damage(1.0); -/// println!("Total (mut): {}", health.total()); -/// } -/// } -/// # bevy_ecs::system::assert_is_system(my_system); -/// ``` -/// -/// ## Deriving traits for query items -/// -/// The `QueryData` derive macro does not automatically implement the traits of the struct to the query item types. -/// Something similar can be done by using the `#[query_data(derive(...))]` attribute. -/// This will apply the listed derivable traits to the query item structs. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::query::QueryData; -/// # -/// # #[derive(Component, Debug)] -/// # struct ComponentA; -/// # -/// #[derive(QueryData)] -/// #[query_data(mutable, derive(Debug))] -/// struct CustomQuery { -/// component_a: &'static ComponentA, -/// } -/// -/// // This function statically checks that `T` implements `Debug`. -/// fn assert_debug() {} -/// -/// assert_debug::(); -/// assert_debug::(); -/// ``` -/// -/// ## Query composition -/// -/// It is possible to use any `QueryData` as a field of another one. -/// This means that a `QueryData` can also be used as a subquery, potentially in multiple places. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::query::QueryData; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # #[derive(Component)] -/// # struct ComponentB; -/// # #[derive(Component)] -/// # struct ComponentC; -/// # -/// #[derive(QueryData)] -/// struct SubQuery { -/// component_a: &'static ComponentA, -/// component_b: &'static ComponentB, -/// } -/// -/// #[derive(QueryData)] -/// struct MyQuery { -/// subquery: SubQuery, -/// component_c: &'static ComponentC, -/// } -/// ``` -/// -/// # Generic Queries -/// -/// When writing generic code, it is often necessary to use [`PhantomData`] -/// to constrain type parameters. Since `QueryData` is implemented for all -/// `PhantomData` types, this pattern can be used with this macro. -/// -/// ``` -/// # use bevy_ecs::{prelude::*, query::QueryData}; -/// # use std::marker::PhantomData; -/// #[derive(QueryData)] -/// pub struct GenericQuery { -/// id: Entity, -/// marker: PhantomData, -/// } -/// # fn my_system(q: Query>) {} -/// # bevy_ecs::system::assert_is_system(my_system); -/// ``` -/// -/// # Safety -/// -/// - Component access of `Self::ReadOnly` must be a subset of `Self` -/// and `Self::ReadOnly` must match exactly the same archetypes/tables as `Self` -/// - `IS_READ_ONLY` must be `true` if and only if `Self: ReadOnlyQueryData` -/// -/// [`Query`]: crate::system::Query -/// [`ReadOnly`]: Self::ReadOnly -#[diagnostic::on_unimplemented( - message = "`{Self}` is not valid to request as data in a `Query`", - label = "invalid `Query` data", - note = "if `{Self}` is a component type, try using `&{Self}` or `&mut {Self}`" -)] -pub unsafe trait QueryData: WorldQuery { - /// True if this query is read-only and may not perform mutable access. - const IS_READ_ONLY: bool; - - /// The read-only variant of this [`QueryData`], which satisfies the [`ReadOnlyQueryData`] trait. - type ReadOnly: ReadOnlyQueryData::State>; - - /// The item returned by this [`WorldQuery`] - /// This will be the data retrieved by the query, - /// and is visible to the end user when calling e.g. `Query::get`. - type Item<'w, 's>; - - /// This function manually implements subtyping for the query items. - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's>; - - /// Offers additional access above what we requested in `update_component_access`. - /// Implementations may add additional access that is a subset of `available_access` - /// and does not conflict with anything in `access`, - /// and must update `access` to include that access. - /// - /// This is used by [`WorldQuery`] types like [`FilteredEntityRef`] - /// and [`FilteredEntityMut`] to support dynamic access. - /// - /// Called when constructing a [`QueryLens`](crate::system::QueryLens) or calling [`QueryState::from_builder`](super::QueryState::from_builder) - fn provide_extra_access( - _state: &mut Self::State, - _access: &mut Access, - _available_access: &Access, - ) { - } - - /// Fetch [`Self::Item`](`QueryData::Item`) for either the given `entity` in the current [`Table`], - /// or for the given `entity` in the current [`Archetype`]. This must always be called after - /// [`WorldQuery::set_table`] with a `table_row` in the range of the current [`Table`] or after - /// [`WorldQuery::set_archetype`] with an `entity` in the current archetype. - /// Accesses components registered in [`WorldQuery::update_component_access`]. - /// - /// # Safety - /// - /// - Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and - /// `table_row` must be in the range of the current table and archetype. - /// - There must not be simultaneous conflicting component access registered in `update_component_access`. - unsafe fn fetch<'w, 's>( - state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w, 's>; -} - -/// A [`QueryData`] that is read only. -/// -/// # Safety -/// -/// This must only be implemented for read-only [`QueryData`]'s. -pub unsafe trait ReadOnlyQueryData: QueryData {} - -/// The item type returned when a [`WorldQuery`] is iterated over -pub type QueryItem<'w, 's, Q> = ::Item<'w, 's>; -/// The read-only variant of the item type returned when a [`QueryData`] is iterated over immutably -pub type ROQueryItem<'w, 's, D> = QueryItem<'w, 's, ::ReadOnly>; - -/// A [`QueryData`] that does not borrow from its [`QueryState`](crate::query::QueryState). -/// -/// This is implemented by most `QueryData` types. -/// The main exceptions are [`FilteredEntityRef`], [`FilteredEntityMut`], [`EntityRefExcept`], and [`EntityMutExcept`], -/// which borrow an access list from their query state. -/// Consider using a full [`EntityRef`] or [`EntityMut`] if you would need those. -pub trait ReleaseStateQueryData: QueryData { - /// Releases the borrow from the query state by converting an item to have a `'static` state lifetime. - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static>; -} - -/// SAFETY: -/// `update_component_access` does nothing. -/// This is sound because `fetch` does not access components. -unsafe impl WorldQuery for Entity { - type Fetch<'w> = (); - type State = (); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} - - unsafe fn init_fetch<'w, 's>( - _world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> Self::Fetch<'w> { - } - - const IS_DENSE: bool = true; - - #[inline] - unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _archetype: &'w Archetype, - _table: &Table, - ) { - } - - #[inline] - unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _table: &'w Table, - ) { - } - - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - - fn init_state(_world: &mut World) {} - - fn get_state(_components: &Components) -> Option<()> { - Some(()) - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl QueryData for Entity { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - - type Item<'w, 's> = Entity; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - _fetch: &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - entity - } -} - -/// SAFETY: access is read only -unsafe impl ReadOnlyQueryData for Entity {} - -impl ReleaseStateQueryData for Entity { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -/// SAFETY: -/// `update_component_access` does nothing. -/// This is sound because `fetch` does not access components. -unsafe impl WorldQuery for EntityLocation { - type Fetch<'w> = &'w Entities; - type State = (); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> Self::Fetch<'w> { - world.entities() - } - - // This is set to true to avoid forcing archetypal iteration in compound queries, is likely to be slower - // in most practical use case. - const IS_DENSE: bool = true; - - #[inline] - unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _archetype: &'w Archetype, - _table: &Table, - ) { - } - - #[inline] - unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _table: &'w Table, - ) { - } - - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - - fn init_state(_world: &mut World) {} - - fn get_state(_components: &Components) -> Option<()> { - Some(()) - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl QueryData for EntityLocation { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = EntityLocation; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - // SAFETY: `fetch` must be called with an entity that exists in the world - unsafe { fetch.get(entity).debug_checked_unwrap() } - } -} - -/// SAFETY: access is read only -unsafe impl ReadOnlyQueryData for EntityLocation {} - -impl ReleaseStateQueryData for EntityLocation { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -/// The `SpawnDetails` query parameter fetches the [`Tick`] the entity was spawned at. -/// -/// To evaluate whether the spawn happened since the last time the system ran, the system -/// param [`SystemChangeTick`](bevy_ecs::system::SystemChangeTick) needs to be used. -/// -/// If the query should filter for spawned entities instead, use the -/// [`Spawned`](bevy_ecs::query::Spawned) query filter instead. -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::entity::Entity; -/// # use bevy_ecs::system::Query; -/// # use bevy_ecs::query::Spawned; -/// # use bevy_ecs::query::SpawnDetails; -/// -/// fn print_spawn_details(query: Query<(Entity, SpawnDetails)>) { -/// for (entity, spawn_details) in &query { -/// if spawn_details.is_spawned() { -/// print!("new "); -/// } -/// print!( -/// "entity {:?} spawned at {:?}", -/// entity, -/// spawn_details.spawned_at() -/// ); -/// match spawn_details.spawned_by().into_option() { -/// Some(location) => println!(" by {:?}", location), -/// None => println!() -/// } -/// } -/// } -/// -/// # bevy_ecs::system::assert_is_system(print_spawn_details); -/// ``` -#[derive(Clone, Copy, Debug)] -pub struct SpawnDetails { - spawned_by: MaybeLocation, - spawned_at: Tick, - last_run: Tick, - this_run: Tick, -} - -impl SpawnDetails { - /// Returns `true` if the entity spawned since the last time this system ran. - /// Otherwise, returns `false`. - pub fn is_spawned(self) -> bool { - self.spawned_at.is_newer_than(self.last_run, self.this_run) - } - - /// Returns the `Tick` this entity spawned at. - pub fn spawned_at(self) -> Tick { - self.spawned_at - } - - /// Returns the source code location from which this entity has been spawned. - pub fn spawned_by(self) -> MaybeLocation { - self.spawned_by - } -} - -#[doc(hidden)] -#[derive(Clone)] -pub struct SpawnDetailsFetch<'w> { - entities: &'w Entities, - last_run: Tick, - this_run: Tick, -} - -// SAFETY: -// No components are accessed. -unsafe impl WorldQuery for SpawnDetails { - type Fetch<'w> = SpawnDetailsFetch<'w>; - type State = (); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - SpawnDetailsFetch { - entities: world.entities(), - last_run, - this_run, - } - } - - const IS_DENSE: bool = true; - - #[inline] - unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _archetype: &'w Archetype, - _table: &'w Table, - ) { - } - - #[inline] - unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _table: &'w Table, - ) { - } - - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - - fn init_state(_world: &mut World) {} - - fn get_state(_components: &Components) -> Option<()> { - Some(()) - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -// SAFETY: -// No components are accessed. -// Is its own ReadOnlyQueryData. -unsafe impl QueryData for SpawnDetails { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = Self; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - // SAFETY: only living entities are queried - let (spawned_by, spawned_at) = unsafe { - fetch - .entities - .entity_get_spawned_or_despawned_unchecked(entity) - }; - Self { - spawned_by, - spawned_at, - last_run: fetch.last_run, - this_run: fetch.this_run, - } - } -} - -/// SAFETY: access is read only -unsafe impl ReadOnlyQueryData for SpawnDetails {} - -impl ReleaseStateQueryData for SpawnDetails { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -/// The [`WorldQuery::Fetch`] type for WorldQueries that can fetch multiple components from an entity -/// ([`EntityRef`], [`EntityMut`], etc.) -#[derive(Copy, Clone)] -#[doc(hidden)] -pub struct EntityFetch<'w> { - world: UnsafeWorldCell<'w>, - last_run: Tick, - this_run: Tick, -} - -/// SAFETY: -/// `fetch` accesses all components in a readonly way. -/// This is sound because `update_component_access` sets read access for all components and panic when appropriate. -/// Filters are unchanged. -unsafe impl<'a> WorldQuery for EntityRef<'a> { - type Fetch<'w> = EntityFetch<'w>; - type State = (); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - EntityFetch { - world, - last_run, - this_run, - } - } - - const IS_DENSE: bool = true; - - #[inline] - unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _archetype: &'w Archetype, - _table: &Table, - ) { - } - - #[inline] - unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _table: &'w Table, - ) { - } - - fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { - assert!( - !access.access().has_any_component_write(), - "EntityRef conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", - ); - access.read_all_components(); - } - - fn init_state(_world: &mut World) {} - - fn get_state(_components: &Components) -> Option<()> { - Some(()) - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl<'a> QueryData for EntityRef<'a> { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = EntityRef<'w>; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - // SAFETY: `fetch` must be called with an entity that exists in the world - let cell = unsafe { - fetch - .world - .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) - .debug_checked_unwrap() - }; - // SAFETY: Read-only access to every component has been registered. - unsafe { EntityRef::new(cell) } - } -} - -/// SAFETY: access is read only -unsafe impl ReadOnlyQueryData for EntityRef<'_> {} - -impl ReleaseStateQueryData for EntityRef<'_> { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` -unsafe impl<'a> WorldQuery for EntityMut<'a> { - type Fetch<'w> = EntityFetch<'w>; - type State = (); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - EntityFetch { - world, - last_run, - this_run, - } - } - - const IS_DENSE: bool = true; - - #[inline] - unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _archetype: &'w Archetype, - _table: &Table, - ) { - } - - #[inline] - unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _table: &'w Table, - ) { - } - - fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { - assert!( - !access.access().has_any_component_read(), - "EntityMut conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.", - ); - access.write_all_components(); - } - - fn init_state(_world: &mut World) {} - - fn get_state(_components: &Components) -> Option<()> { - Some(()) - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -/// SAFETY: access of `EntityRef` is a subset of `EntityMut` -unsafe impl<'a> QueryData for EntityMut<'a> { - const IS_READ_ONLY: bool = false; - type ReadOnly = EntityRef<'a>; - type Item<'w, 's> = EntityMut<'w>; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - // SAFETY: `fetch` must be called with an entity that exists in the world - let cell = unsafe { - fetch - .world - .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) - .debug_checked_unwrap() - }; - // SAFETY: mutable access to every component has been registered. - unsafe { EntityMut::new(cell) } - } -} - -impl ReleaseStateQueryData for EntityMut<'_> { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` -unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { - type Fetch<'w> = (EntityFetch<'w>, Access); - type State = Access; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - const IS_DENSE: bool = false; - - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - let mut access = Access::default(); - access.read_all_components(); - ( - EntityFetch { - world, - last_run, - this_run, - }, - access, - ) - } - - #[inline] - unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w>, - state: &'s Self::State, - _: &'w Archetype, - _table: &Table, - ) { - fetch.1.clone_from(state); - } - - #[inline] - unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w>, state: &'s Self::State, _: &'w Table) { - fetch.1.clone_from(state); - } - - fn update_component_access( - state: &Self::State, - filtered_access: &mut FilteredAccess, - ) { - assert!( - filtered_access.access().is_compatible(state), - "FilteredEntityRef conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.", - ); - filtered_access.access.extend(state); - } - - fn init_state(_world: &mut World) -> Self::State { - Access::default() - } - - fn get_state(_components: &Components) -> Option { - Some(Access::default()) - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl<'a> QueryData for FilteredEntityRef<'a> { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = FilteredEntityRef<'w>; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline] - fn provide_extra_access( - state: &mut Self::State, - access: &mut Access, - available_access: &Access, - ) { - // Claim any extra access that doesn't conflict with other subqueries - // This is used when constructing a `QueryLens` or creating a query from a `QueryBuilder` - // Start with the entire available access, since that is the most we can possibly access - state.clone_from(available_access); - // Prevent all writes, since `FilteredEntityRef` only performs read access - state.clear_writes(); - // Prevent any access that would conflict with other accesses in the current query - state.remove_conflicting_access(access); - // Finally, add the resulting access to the query access - // to make sure a later `FilteredEntityMut` won't conflict with this. - access.extend(state); - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - (fetch, access): &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - // SAFETY: `fetch` must be called with an entity that exists in the world - let cell = unsafe { - fetch - .world - .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) - .debug_checked_unwrap() - }; - // SAFETY: mutable access to every component has been registered. - unsafe { FilteredEntityRef::new(cell, access.clone()) } - } -} - -/// SAFETY: Access is read-only. -unsafe impl ReadOnlyQueryData for FilteredEntityRef<'_> {} - -/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` -unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { - type Fetch<'w> = (EntityFetch<'w>, Access); - type State = Access; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - const IS_DENSE: bool = false; - - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - let mut access = Access::default(); - access.write_all_components(); - ( - EntityFetch { - world, - last_run, - this_run, - }, - access, - ) - } - - #[inline] - unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w>, - state: &'s Self::State, - _: &'w Archetype, - _table: &Table, - ) { - fetch.1.clone_from(state); - } - - #[inline] - unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w>, state: &'s Self::State, _: &'w Table) { - fetch.1.clone_from(state); - } - - fn update_component_access( - state: &Self::State, - filtered_access: &mut FilteredAccess, - ) { - assert!( - filtered_access.access().is_compatible(state), - "FilteredEntityMut conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.", - ); - filtered_access.access.extend(state); - } - - fn init_state(_world: &mut World) -> Self::State { - Access::default() - } - - fn get_state(_components: &Components) -> Option { - Some(Access::default()) - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -/// SAFETY: access of `FilteredEntityRef` is a subset of `FilteredEntityMut` -unsafe impl<'a> QueryData for FilteredEntityMut<'a> { - const IS_READ_ONLY: bool = false; - type ReadOnly = FilteredEntityRef<'a>; - type Item<'w, 's> = FilteredEntityMut<'w>; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline] - fn provide_extra_access( - state: &mut Self::State, - access: &mut Access, - available_access: &Access, - ) { - // Claim any extra access that doesn't conflict with other subqueries - // This is used when constructing a `QueryLens` or creating a query from a `QueryBuilder` - // Start with the entire available access, since that is the most we can possibly access - state.clone_from(available_access); - // Prevent any access that would conflict with other accesses in the current query - state.remove_conflicting_access(access); - // Finally, add the resulting access to the query access - // to make sure a later `FilteredEntityRef` or `FilteredEntityMut` won't conflict with this. - access.extend(state); - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - (fetch, access): &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - // SAFETY: `fetch` must be called with an entity that exists in the world - let cell = unsafe { - fetch - .world - .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) - .debug_checked_unwrap() - }; - // SAFETY: mutable access to every component has been registered. - unsafe { FilteredEntityMut::new(cell, access.clone()) } - } -} - -/// SAFETY: `EntityRefExcept` guards access to all components in the bundle `B` -/// and populates `Access` values so that queries that conflict with this access -/// are rejected. -unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B> -where - B: Bundle, -{ - type Fetch<'w> = EntityFetch<'w>; - type State = SmallVec<[ComponentId; 4]>; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - _: &'s Self::State, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - EntityFetch { - world, - last_run, - this_run, - } - } - - const IS_DENSE: bool = true; - - unsafe fn set_archetype<'w, 's>( - _: &mut Self::Fetch<'w>, - _: &'s Self::State, - _: &'w Archetype, - _: &'w Table, - ) { - } - - unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w>, _: &'s Self::State, _: &'w Table) {} - - fn update_component_access( - state: &Self::State, - filtered_access: &mut FilteredAccess, - ) { - let mut my_access = Access::new(); - my_access.read_all_components(); - for id in state { - my_access.remove_component_read(*id); - } - - let access = filtered_access.access_mut(); - assert!( - access.is_compatible(&my_access), - "`EntityRefExcept<{}>` conflicts with a previous access in this query.", - DebugName::type_name::(), - ); - access.extend(&my_access); - } - - fn init_state(world: &mut World) -> Self::State { - Self::get_state(world.components()).unwrap() - } - - fn get_state(components: &Components) -> Option { - let mut ids = SmallVec::new(); - B::get_component_ids(components, &mut |maybe_id| { - if let Some(id) = maybe_id { - ids.push(id); - } - }); - Some(ids) - } - - fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool { - true - } -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly`. -unsafe impl<'a, B> QueryData for EntityRefExcept<'a, B> -where - B: Bundle, -{ - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = EntityRefExcept<'w, B>; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - _: TableRow, - ) -> Self::Item<'w, 's> { - let cell = fetch - .world - .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) - .unwrap(); - EntityRefExcept::new(cell) - } -} - -/// SAFETY: `EntityRefExcept` enforces read-only access to its contained -/// components. -unsafe impl<'a, B> ReadOnlyQueryData for EntityRefExcept<'a, B> where B: Bundle {} - -/// SAFETY: `EntityMutExcept` guards access to all components in the bundle `B` -/// and populates `Access` values so that queries that conflict with this access -/// are rejected. -unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B> -where - B: Bundle, -{ - type Fetch<'w> = EntityFetch<'w>; - type State = SmallVec<[ComponentId; 4]>; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - _: &'s Self::State, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - EntityFetch { - world, - last_run, - this_run, - } - } - - const IS_DENSE: bool = true; - - unsafe fn set_archetype<'w, 's>( - _: &mut Self::Fetch<'w>, - _: &'s Self::State, - _: &'w Archetype, - _: &'w Table, - ) { - } - - unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w>, _: &'s Self::State, _: &'w Table) {} - - fn update_component_access( - state: &Self::State, - filtered_access: &mut FilteredAccess, - ) { - let mut my_access = Access::new(); - my_access.write_all_components(); - for id in state { - my_access.remove_component_read(*id); - } - - let access = filtered_access.access_mut(); - assert!( - access.is_compatible(&my_access), - "`EntityMutExcept<{}>` conflicts with a previous access in this query.", - DebugName::type_name::() - ); - access.extend(&my_access); - } - - fn init_state(world: &mut World) -> Self::State { - Self::get_state(world.components()).unwrap() - } - - fn get_state(components: &Components) -> Option { - let mut ids = SmallVec::new(); - B::get_component_ids(components, &mut |maybe_id| { - if let Some(id) = maybe_id { - ids.push(id); - } - }); - Some(ids) - } - - fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool { - true - } -} - -/// SAFETY: All accesses that `EntityRefExcept` provides are also accesses that -/// `EntityMutExcept` provides. -unsafe impl<'a, B> QueryData for EntityMutExcept<'a, B> -where - B: Bundle, -{ - const IS_READ_ONLY: bool = false; - type ReadOnly = EntityRefExcept<'a, B>; - type Item<'w, 's> = EntityMutExcept<'w, B>; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - _: TableRow, - ) -> Self::Item<'w, 's> { - let cell = fetch - .world - .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) - .unwrap(); - EntityMutExcept::new(cell) - } -} - -/// SAFETY: -/// `update_component_access` does nothing. -/// This is sound because `fetch` does not access components. -unsafe impl WorldQuery for &Archetype { - type Fetch<'w> = (&'w Entities, &'w Archetypes); - type State = (); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> Self::Fetch<'w> { - (world.entities(), world.archetypes()) - } - - // This could probably be a non-dense query and just set a Option<&Archetype> fetch value in - // set_archetypes, but forcing archetypal iteration is likely to be slower in any compound query. - const IS_DENSE: bool = true; - - #[inline] - unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _archetype: &'w Archetype, - _table: &Table, - ) { - } - - #[inline] - unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _table: &'w Table, - ) { - } - - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - - fn init_state(_world: &mut World) {} - - fn get_state(_components: &Components) -> Option<()> { - Some(()) - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl QueryData for &Archetype { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = &'w Archetype; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - let (entities, archetypes) = *fetch; - // SAFETY: `fetch` must be called with an entity that exists in the world - let location = unsafe { entities.get(entity).debug_checked_unwrap() }; - // SAFETY: The assigned archetype for a living entity must always be valid. - unsafe { archetypes.get(location.archetype_id).debug_checked_unwrap() } - } -} - -/// SAFETY: access is read only -unsafe impl ReadOnlyQueryData for &Archetype {} - -impl ReleaseStateQueryData for &Archetype { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -/// The [`WorldQuery::Fetch`] type for `& T`. -pub struct ReadFetch<'w, T: Component> { - components: StorageSwitch< - T, - // T::STORAGE_TYPE = StorageType::Table - Option>>, - // T::STORAGE_TYPE = StorageType::SparseSet - Option<&'w ComponentSparseSet>, - >, -} - -impl Clone for ReadFetch<'_, T> { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for ReadFetch<'_, T> {} - -/// SAFETY: -/// `fetch` accesses a single component in a readonly way. -/// This is sound because `update_component_access` adds read access for that component and panic when appropriate. -/// `update_component_access` adds a `With` filter for a component. -/// This is sound because `matches_component_set` returns whether the set contains that component. -unsafe impl WorldQuery for &T { - type Fetch<'w> = ReadFetch<'w, T>; - type State = ComponentId; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - #[inline] - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - &component_id: &ComponentId, - _last_run: Tick, - _this_run: Tick, - ) -> ReadFetch<'w, T> { - ReadFetch { - components: StorageSwitch::new( - || None, - || { - // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_component_access`. - // Note that we do not actually access any components in this function, we just get a shared - // reference to the sparse set, which is used to access the components in `Self::fetch`. - unsafe { world.storages().sparse_sets.get(component_id) } - }, - ), - } - } - - const IS_DENSE: bool = { - match T::STORAGE_TYPE { - StorageType::Table => true, - StorageType::SparseSet => false, - } - }; - - #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut ReadFetch<'w, T>, - component_id: &ComponentId, - _archetype: &'w Archetype, - table: &'w Table, - ) { - if Self::IS_DENSE { - // SAFETY: `set_archetype`'s safety rules are a super set of the `set_table`'s ones. - unsafe { - Self::set_table(fetch, component_id, table); - } - } - } - - #[inline] - unsafe fn set_table<'w>( - fetch: &mut ReadFetch<'w, T>, - &component_id: &ComponentId, - table: &'w Table, - ) { - let table_data = Some( - table - .get_data_slice_for(component_id) - .debug_checked_unwrap() - .into(), - ); - // SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table - unsafe { fetch.components.set_table(table_data) }; - } - - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { - assert!( - !access.access().has_component_write(component_id), - "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", - DebugName::type_name::(), - ); - access.add_component_read(component_id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set( - &state: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(state) - } -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl QueryData for &T { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = &'w T; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w, 's> { - fetch.components.extract( - |table| { - // SAFETY: set_table was previously called - let table = unsafe { table.debug_checked_unwrap() }; - // SAFETY: Caller ensures `table_row` is in range. - let item = unsafe { table.get(table_row.index()) }; - item.deref() - }, - |sparse_set| { - // SAFETY: Caller ensures `entity` is in range. - let item = unsafe { - sparse_set - .debug_checked_unwrap() - .get(entity) - .debug_checked_unwrap() - }; - item.deref() - }, - ) - } -} - -/// SAFETY: access is read only -unsafe impl ReadOnlyQueryData for &T {} - -impl ReleaseStateQueryData for &T { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -#[doc(hidden)] -pub struct RefFetch<'w, T: Component> { - components: StorageSwitch< - T, - // T::STORAGE_TYPE = StorageType::Table - Option<( - ThinSlicePtr<'w, UnsafeCell>, - ThinSlicePtr<'w, UnsafeCell>, - ThinSlicePtr<'w, UnsafeCell>, - MaybeLocation>>>, - )>, - // T::STORAGE_TYPE = StorageType::SparseSet - // Can be `None` when the component has never been inserted - Option<&'w ComponentSparseSet>, - >, - last_run: Tick, - this_run: Tick, -} - -impl Clone for RefFetch<'_, T> { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for RefFetch<'_, T> {} - -/// SAFETY: -/// `fetch` accesses a single component in a readonly way. -/// This is sound because `update_component_access` adds read access for that component and panic when appropriate. -/// `update_component_access` adds a `With` filter for a component. -/// This is sound because `matches_component_set` returns whether the set contains that component. -unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { - type Fetch<'w> = RefFetch<'w, T>; - type State = ComponentId; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - #[inline] - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - &component_id: &ComponentId, - last_run: Tick, - this_run: Tick, - ) -> RefFetch<'w, T> { - RefFetch { - components: StorageSwitch::new( - || None, - || { - // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_component_access`. - // Note that we do not actually access any components in this function, we just get a shared - // reference to the sparse set, which is used to access the components in `Self::fetch`. - unsafe { world.storages().sparse_sets.get(component_id) } - }, - ), - last_run, - this_run, - } - } - - const IS_DENSE: bool = { - match T::STORAGE_TYPE { - StorageType::Table => true, - StorageType::SparseSet => false, - } - }; - - #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut RefFetch<'w, T>, - component_id: &ComponentId, - _archetype: &'w Archetype, - table: &'w Table, - ) { - if Self::IS_DENSE { - // SAFETY: `set_archetype`'s safety rules are a super set of the `set_table`'s ones. - unsafe { - Self::set_table(fetch, component_id, table); - } - } - } - - #[inline] - unsafe fn set_table<'w>( - fetch: &mut RefFetch<'w, T>, - &component_id: &ComponentId, - table: &'w Table, - ) { - let column = table.get_column(component_id).debug_checked_unwrap(); - let table_data = Some(( - column.get_data_slice(table.entity_count() as usize).into(), - column - .get_added_ticks_slice(table.entity_count() as usize) - .into(), - column - .get_changed_ticks_slice(table.entity_count() as usize) - .into(), - column - .get_changed_by_slice(table.entity_count() as usize) - .map(Into::into), - )); - // SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table - unsafe { fetch.components.set_table(table_data) }; - } - - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { - assert!( - !access.access().has_component_write(component_id), - "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", - DebugName::type_name::(), - ); - access.add_component_read(component_id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set( - &state: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(state) - } -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = Ref<'w, T>; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w, 's> { - fetch.components.extract( - |table| { - // SAFETY: set_table was previously called - let (table_components, added_ticks, changed_ticks, callers) = - unsafe { table.debug_checked_unwrap() }; - - // SAFETY: The caller ensures `table_row` is in range. - let component = unsafe { table_components.get(table_row.index()) }; - // SAFETY: The caller ensures `table_row` is in range. - let added = unsafe { added_ticks.get(table_row.index()) }; - // SAFETY: The caller ensures `table_row` is in range. - let changed = unsafe { changed_ticks.get(table_row.index()) }; - // SAFETY: The caller ensures `table_row` is in range. - let caller = callers.map(|callers| unsafe { callers.get(table_row.index()) }); - - Ref { - value: component.deref(), - ticks: Ticks { - added: added.deref(), - changed: changed.deref(), - this_run: fetch.this_run, - last_run: fetch.last_run, - }, - changed_by: caller.map(|caller| caller.deref()), - } - }, - |sparse_set| { - // SAFETY: The caller ensures `entity` is in range and has the component. - let (component, ticks, caller) = unsafe { - sparse_set - .debug_checked_unwrap() - .get_with_ticks(entity) - .debug_checked_unwrap() - }; - - Ref { - value: component.deref(), - ticks: Ticks::from_tick_cells(ticks, fetch.last_run, fetch.this_run), - changed_by: caller.map(|caller| caller.deref()), - } - }, - ) - } -} - -/// SAFETY: access is read only -unsafe impl<'__w, T: Component> ReadOnlyQueryData for Ref<'__w, T> {} - -impl ReleaseStateQueryData for Ref<'_, T> { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -/// The [`WorldQuery::Fetch`] type for `&mut T`. -pub struct WriteFetch<'w, T: Component> { - components: StorageSwitch< - T, - // T::STORAGE_TYPE = StorageType::Table - Option<( - ThinSlicePtr<'w, UnsafeCell>, - ThinSlicePtr<'w, UnsafeCell>, - ThinSlicePtr<'w, UnsafeCell>, - MaybeLocation>>>, - )>, - // T::STORAGE_TYPE = StorageType::SparseSet - // Can be `None` when the component has never been inserted - Option<&'w ComponentSparseSet>, - >, - last_run: Tick, - this_run: Tick, -} - -impl Clone for WriteFetch<'_, T> { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for WriteFetch<'_, T> {} - -/// SAFETY: -/// `fetch` accesses a single component mutably. -/// This is sound because `update_component_access` adds write access for that component and panic when appropriate. -/// `update_component_access` adds a `With` filter for a component. -/// This is sound because `matches_component_set` returns whether the set contains that component. -unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { - type Fetch<'w> = WriteFetch<'w, T>; - type State = ComponentId; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - #[inline] - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - &component_id: &ComponentId, - last_run: Tick, - this_run: Tick, - ) -> WriteFetch<'w, T> { - WriteFetch { - components: StorageSwitch::new( - || None, - || { - // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_component_access`. - // Note that we do not actually access any components in this function, we just get a shared - // reference to the sparse set, which is used to access the components in `Self::fetch`. - unsafe { world.storages().sparse_sets.get(component_id) } - }, - ), - last_run, - this_run, - } - } - - const IS_DENSE: bool = { - match T::STORAGE_TYPE { - StorageType::Table => true, - StorageType::SparseSet => false, - } - }; - - #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut WriteFetch<'w, T>, - component_id: &ComponentId, - _archetype: &'w Archetype, - table: &'w Table, - ) { - if Self::IS_DENSE { - // SAFETY: `set_archetype`'s safety rules are a super set of the `set_table`'s ones. - unsafe { - Self::set_table(fetch, component_id, table); - } - } - } - - #[inline] - unsafe fn set_table<'w>( - fetch: &mut WriteFetch<'w, T>, - &component_id: &ComponentId, - table: &'w Table, - ) { - let column = table.get_column(component_id).debug_checked_unwrap(); - let table_data = Some(( - column.get_data_slice(table.entity_count() as usize).into(), - column - .get_added_ticks_slice(table.entity_count() as usize) - .into(), - column - .get_changed_ticks_slice(table.entity_count() as usize) - .into(), - column - .get_changed_by_slice(table.entity_count() as usize) - .map(Into::into), - )); - // SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table - unsafe { fetch.components.set_table(table_data) }; - } - - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { - assert!( - !access.access().has_component_read(component_id), - "&mut {} conflicts with a previous access in this query. Mutable component access must be unique.", - DebugName::type_name::(), - ); - access.add_component_write(component_id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set( - &state: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(state) - } -} - -/// SAFETY: access of `&T` is a subset of `&mut T` -unsafe impl<'__w, T: Component> QueryData for &'__w mut T { - const IS_READ_ONLY: bool = false; - type ReadOnly = &'__w T; - type Item<'w, 's> = Mut<'w, T>; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w, 's> { - fetch.components.extract( - |table| { - // SAFETY: set_table was previously called - let (table_components, added_ticks, changed_ticks, callers) = - unsafe { table.debug_checked_unwrap() }; - - // SAFETY: The caller ensures `table_row` is in range. - let component = unsafe { table_components.get(table_row.index()) }; - // SAFETY: The caller ensures `table_row` is in range. - let added = unsafe { added_ticks.get(table_row.index()) }; - // SAFETY: The caller ensures `table_row` is in range. - let changed = unsafe { changed_ticks.get(table_row.index()) }; - // SAFETY: The caller ensures `table_row` is in range. - let caller = callers.map(|callers| unsafe { callers.get(table_row.index()) }); - - Mut { - value: component.deref_mut(), - ticks: TicksMut { - added: added.deref_mut(), - changed: changed.deref_mut(), - this_run: fetch.this_run, - last_run: fetch.last_run, - }, - changed_by: caller.map(|caller| caller.deref_mut()), - } - }, - |sparse_set| { - // SAFETY: The caller ensures `entity` is in range and has the component. - let (component, ticks, caller) = unsafe { - sparse_set - .debug_checked_unwrap() - .get_with_ticks(entity) - .debug_checked_unwrap() - }; - - Mut { - value: component.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells(ticks, fetch.last_run, fetch.this_run), - changed_by: caller.map(|caller| caller.deref_mut()), - } - }, - ) - } -} - -impl> ReleaseStateQueryData for &mut T { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -/// When `Mut` is used in a query, it will be converted to `Ref` when transformed into its read-only form, providing access to change detection methods. -/// -/// By contrast `&mut T` will result in a `Mut` item in mutable form to record mutations, but result in a bare `&T` in read-only form. -/// -/// SAFETY: -/// `fetch` accesses a single component mutably. -/// This is sound because `update_component_access` adds write access for that component and panic when appropriate. -/// `update_component_access` adds a `With` filter for a component. -/// This is sound because `matches_component_set` returns whether the set contains that component. -unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { - type Fetch<'w> = WriteFetch<'w, T>; - type State = ComponentId; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - #[inline] - // Forwarded to `&mut T` - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - state: &ComponentId, - last_run: Tick, - this_run: Tick, - ) -> WriteFetch<'w, T> { - <&mut T as WorldQuery>::init_fetch(world, state, last_run, this_run) - } - - // Forwarded to `&mut T` - const IS_DENSE: bool = <&mut T as WorldQuery>::IS_DENSE; - - #[inline] - // Forwarded to `&mut T` - unsafe fn set_archetype<'w>( - fetch: &mut WriteFetch<'w, T>, - state: &ComponentId, - archetype: &'w Archetype, - table: &'w Table, - ) { - <&mut T as WorldQuery>::set_archetype(fetch, state, archetype, table); - } - - #[inline] - // Forwarded to `&mut T` - unsafe fn set_table<'w>(fetch: &mut WriteFetch<'w, T>, state: &ComponentId, table: &'w Table) { - <&mut T as WorldQuery>::set_table(fetch, state, table); - } - - // NOT forwarded to `&mut T` - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { - // Update component access here instead of in `<&mut T as WorldQuery>` to avoid erroneously referencing - // `&mut T` in error message. - assert!( - !access.access().has_component_read(component_id), - "Mut<{}> conflicts with a previous access in this query. Mutable component access mut be unique.", - DebugName::type_name::(), - ); - access.add_component_write(component_id); - } - - // Forwarded to `&mut T` - fn init_state(world: &mut World) -> ComponentId { - <&mut T as WorldQuery>::init_state(world) - } - - // Forwarded to `&mut T` - fn get_state(components: &Components) -> Option { - <&mut T as WorldQuery>::get_state(components) - } - - // Forwarded to `&mut T` - fn matches_component_set( - state: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - <&mut T as WorldQuery>::matches_component_set(state, set_contains_id) - } -} - -// SAFETY: access of `Ref` is a subset of `Mut` -unsafe impl<'__w, T: Component> QueryData for Mut<'__w, T> { - const IS_READ_ONLY: bool = false; - type ReadOnly = Ref<'__w, T>; - type Item<'w, 's> = Mut<'w, T>; - - // Forwarded to `&mut T` - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - <&mut T as QueryData>::shrink(item) - } - - #[inline(always)] - // Forwarded to `&mut T` - unsafe fn fetch<'w, 's>( - state: &'s Self::State, - // Rust complains about lifetime bounds not matching the trait if I directly use `WriteFetch<'w, T>` right here. - // But it complains nowhere else in the entire trait implementation. - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w, 's> { - <&mut T as QueryData>::fetch(state, fetch, entity, table_row) - } -} - -impl> ReleaseStateQueryData for Mut<'_, T> { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -#[doc(hidden)] -pub struct OptionFetch<'w, T: WorldQuery> { - fetch: T::Fetch<'w>, - matches: bool, -} - -impl Clone for OptionFetch<'_, T> { - fn clone(&self) -> Self { - Self { - fetch: self.fetch.clone(), - matches: self.matches, - } - } -} - -/// SAFETY: -/// `fetch` might access any components that `T` accesses. -/// This is sound because `update_component_access` adds the same accesses as `T`. -/// Filters are unchanged. -unsafe impl WorldQuery for Option { - type Fetch<'w> = OptionFetch<'w, T>; - type State = T::State; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - OptionFetch { - fetch: T::shrink_fetch(fetch.fetch), - matches: fetch.matches, - } - } - - #[inline] - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - state: &'s T::State, - last_run: Tick, - this_run: Tick, - ) -> OptionFetch<'w, T> { - OptionFetch { - // SAFETY: The invariants are upheld by the caller. - fetch: unsafe { T::init_fetch(world, state, last_run, this_run) }, - matches: false, - } - } - - const IS_DENSE: bool = T::IS_DENSE; - - #[inline] - unsafe fn set_archetype<'w, 's>( - fetch: &mut OptionFetch<'w, T>, - state: &'s T::State, - archetype: &'w Archetype, - table: &'w Table, - ) { - fetch.matches = T::matches_component_set(state, &|id| archetype.contains(id)); - if fetch.matches { - // SAFETY: The invariants are upheld by the caller. - unsafe { - T::set_archetype(&mut fetch.fetch, state, archetype, table); - } - } - } - - #[inline] - unsafe fn set_table<'w, 's>( - fetch: &mut OptionFetch<'w, T>, - state: &'s T::State, - table: &'w Table, - ) { - fetch.matches = T::matches_component_set(state, &|id| table.has_column(id)); - if fetch.matches { - // SAFETY: The invariants are upheld by the caller. - unsafe { - T::set_table(&mut fetch.fetch, state, table); - } - } - } - - fn update_component_access(state: &T::State, access: &mut FilteredAccess) { - // FilteredAccess::add_[write,read] adds the component to the `with` filter. - // Those methods are called on `access` in `T::update_component_access`. - // But in `Option`, we specifically don't filter on `T`, - // since `(Option, &OtherComponent)` should be a valid item, even - // if `Option` is `None`. - // - // We pass a clone of the `FilteredAccess` to `T`, and only update the `Access` - // using `extend_access` so that we can apply `T`'s component_access - // without updating the `with` filters of `access`. - let mut intermediate = access.clone(); - T::update_component_access(state, &mut intermediate); - access.extend_access(&intermediate); - } - - fn init_state(world: &mut World) -> T::State { - T::init_state(world) - } - - fn get_state(components: &Components) -> Option { - T::get_state(components) - } - - fn matches_component_set( - _state: &T::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -// SAFETY: defers to soundness of `T: WorldQuery` impl -unsafe impl QueryData for Option { - const IS_READ_ONLY: bool = T::IS_READ_ONLY; - type ReadOnly = Option; - type Item<'w, 's> = Option>; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item.map(T::shrink) - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w, 's> { - fetch - .matches - // SAFETY: The invariants are upheld by the caller. - .then(|| unsafe { T::fetch(state, &mut fetch.fetch, entity, table_row) }) - } -} - -/// SAFETY: [`OptionFetch`] is read only because `T` is read only -unsafe impl ReadOnlyQueryData for Option {} - -impl ReleaseStateQueryData for Option { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item.map(T::release_state) - } -} - -/// Returns a bool that describes if an entity has the component `T`. -/// -/// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities -/// have the component `T` but don't actually care about the component's value. -/// -/// # Footguns -/// -/// Note that a `Query>` will match all existing entities. -/// Beware! Even if it matches all entities, it doesn't mean that `query.get(entity)` -/// will always return `Ok(bool)`. -/// -/// In the case of a non-existent entity, such as a despawned one, it will return `Err`. -/// A workaround is to replace `query.get(entity).unwrap()` by -/// `query.get(entity).unwrap_or_default()`. -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::query::Has; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::system::Query; -/// # -/// # #[derive(Component)] -/// # struct IsHungry; -/// # #[derive(Component)] -/// # struct Name { name: &'static str }; -/// # -/// fn food_entity_system(query: Query<(&Name, Has) >) { -/// for (name, is_hungry) in &query { -/// if is_hungry{ -/// println!("{} would like some food.", name.name); -/// } else { -/// println!("{} has had sufficient.", name.name); -/// } -/// } -/// } -/// # bevy_ecs::system::assert_is_system(food_entity_system); -/// ``` -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::query::Has; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::system::Query; -/// # -/// # #[derive(Component)] -/// # struct Alpha{has_beta: bool}; -/// # #[derive(Component)] -/// # struct Beta { has_alpha: bool }; -/// # -/// // Unlike `Option<&T>`, `Has` is compatible with `&mut T` -/// // as it does not actually access any data. -/// fn alphabet_entity_system(mut alphas: Query<(&mut Alpha, Has)>, mut betas: Query<(&mut Beta, Has)>) { -/// for (mut alpha, has_beta) in alphas.iter_mut() { -/// alpha.has_beta = has_beta; -/// } -/// for (mut beta, has_alpha) in betas.iter_mut() { -/// beta.has_alpha = has_alpha; -/// } -/// } -/// # bevy_ecs::system::assert_is_system(alphabet_entity_system); -/// ``` -pub struct Has(PhantomData); - -impl core::fmt::Debug for Has { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { - write!(f, "Has<{}>", DebugName::type_name::()) - } -} - -/// SAFETY: -/// `update_component_access` does nothing. -/// This is sound because `fetch` does not access components. -unsafe impl WorldQuery for Has { - type Fetch<'w> = bool; - type State = ComponentId; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - #[inline] - unsafe fn init_fetch<'w, 's>( - _world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> Self::Fetch<'w> { - false - } - - const IS_DENSE: bool = { - match T::STORAGE_TYPE { - StorageType::Table => true, - StorageType::SparseSet => false, - } - }; - - #[inline] - unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w>, - state: &'s Self::State, - archetype: &'w Archetype, - _table: &Table, - ) { - *fetch = archetype.contains(*state); - } - - #[inline] - unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w>, - state: &'s Self::State, - table: &'w Table, - ) { - *fetch = table.has_column(*state); - } - - fn update_component_access( - &component_id: &Self::State, - access: &mut FilteredAccess, - ) { - access.access_mut().add_archetypal(component_id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - // `Has` always matches - true - } -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl QueryData for Has { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = bool; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - *fetch - } -} - -/// SAFETY: [`Has`] is read only -unsafe impl ReadOnlyQueryData for Has {} - -impl ReleaseStateQueryData for Has { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } -} - -/// The `AnyOf` query parameter fetches entities with any of the component types included in T. -/// -/// `Query>` is equivalent to `Query<(Option<&A>, Option<&B>, Option<&mut C>), Or<(With, With, With)>>`. -/// Each of the components in `T` is returned as an `Option`, as with `Option` queries. -/// Entities are guaranteed to have at least one of the components in `T`. -pub struct AnyOf(PhantomData); - -macro_rules! impl_tuple_query_data { - ($(#[$meta:meta])* $(($name: ident, $item: ident, $state: ident)),*) => { - #[expect( - clippy::allow_attributes, - reason = "This is a tuple-related macro; as such the lints below may not always apply." - )] - #[allow( - non_snake_case, - reason = "The names of some variables are provided by the macro's caller, not by us." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." - )] - $(#[$meta])* - // SAFETY: defers to soundness `$name: WorldQuery` impl - unsafe impl<$($name: QueryData),*> QueryData for ($($name,)*) { - const IS_READ_ONLY: bool = true $(&& $name::IS_READ_ONLY)*; - type ReadOnly = ($($name::ReadOnly,)*); - type Item<'w, 's> = ($($name::Item<'w, 's>,)*); - - fn shrink<'wlong: 'wshort, 'wshort, 's>(item: Self::Item<'wlong, 's>) -> Self::Item<'wshort, 's> { - let ($($name,)*) = item; - ($( - $name::shrink($name), - )*) - } - - #[inline] - fn provide_extra_access( - state: &mut Self::State, - access: &mut Access, - available_access: &Access, - ) { - let ($($name,)*) = state; - $($name::provide_extra_access($name, access, available_access);)* - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow - ) -> Self::Item<'w, 's> { - let ($($state,)*) = state; - let ($($name,)*) = fetch; - // SAFETY: The invariants are upheld by the caller. - ($(unsafe { $name::fetch($state, $name, entity, table_row) },)*) - } - } - - /// SAFETY: each item in the tuple is read only - unsafe impl<$($name: ReadOnlyQueryData),*> ReadOnlyQueryData for ($($name,)*) {} - - #[expect( - clippy::allow_attributes, - reason = "This is a tuple-related macro; as such the lints below may not always apply." - )] - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." - )] - impl<$($name: ReleaseStateQueryData),*> ReleaseStateQueryData for ($($name,)*) { - fn release_state<'w>(($($item,)*): Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - ($($name::release_state($item),)*) - } - } - }; -} - -macro_rules! impl_anytuple_fetch { - ($(#[$meta:meta])* $(($name: ident, $state: ident, $item: ident)),*) => { - $(#[$meta])* - #[expect( - clippy::allow_attributes, - reason = "This is a tuple-related macro; as such the lints below may not always apply." - )] - #[allow( - non_snake_case, - reason = "The names of some variables are provided by the macro's caller, not by us." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." - )] - /// SAFETY: - /// `fetch` accesses are a subset of the subqueries' accesses - /// This is sound because `update_component_access` adds accesses according to the implementations of all the subqueries. - /// `update_component_access` replaces the filters with a disjunction where every element is a conjunction of the previous filters and the filters of one of the subqueries. - /// This is sound because `matches_component_set` returns a disjunction of the results of the subqueries' implementations. - unsafe impl<$($name: WorldQuery),*> WorldQuery for AnyOf<($($name,)*)> { - type Fetch<'w> = ($(($name::Fetch<'w>, bool),)*); - type State = ($($name::State,)*); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - let ($($name,)*) = fetch; - ($( - ($name::shrink_fetch($name.0), $name.1), - )*) - } - - #[inline] - unsafe fn init_fetch<'w, 's>(_world: UnsafeWorldCell<'w>, state: &'s Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> { - let ($($name,)*) = state; - // SAFETY: The invariants are upheld by the caller. - ($(( unsafe { $name::init_fetch(_world, $name, _last_run, _this_run) }, false),)*) - } - - const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; - - #[inline] - unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _archetype: &'w Archetype, - _table: &'w Table - ) { - let ($($name,)*) = _fetch; - let ($($state,)*) = _state; - $( - $name.1 = $name::matches_component_set($state, &|id| _archetype.contains(id)); - if $name.1 { - // SAFETY: The invariants are upheld by the caller. - unsafe { $name::set_archetype(&mut $name.0, $state, _archetype, _table); } - } - )* - } - - #[inline] - unsafe fn set_table<'w, 's>(_fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table) { - let ($($name,)*) = _fetch; - let ($($state,)*) = _state; - $( - $name.1 = $name::matches_component_set($state, &|id| _table.has_column(id)); - if $name.1 { - // SAFETY: The invariants are required to be upheld by the caller. - unsafe { $name::set_table(&mut $name.0, $state, _table); } - } - )* - } - - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - // update the filters (Or<(With<$name>,)>) - let ($($name,)*) = state; - - let mut _new_access = FilteredAccess::matches_nothing(); - - $( - // Create an intermediate because `access`'s value needs to be preserved - // for the next query data, and `_new_access` has to be modified only by `append_or` to it, - // which only updates the `filter_sets`, not the `access`. - let mut intermediate = access.clone(); - $name::update_component_access($name, &mut intermediate); - _new_access.append_or(&intermediate); - )* - - // Of the accumulated `_new_access` we only care about the filter sets, not the access. - access.filter_sets = _new_access.filter_sets; - - // For the access we instead delegate to a tuple of `Option`s. - // This has essentially the same semantics of `AnyOf`, except that it doesn't - // require at least one of them to be `Some`. - // We however solve this by setting explicitly the `filter_sets` above. - // Also note that Option updates the `access` but not the `filter_sets`. - <($(Option<$name>,)*)>::update_component_access(state, access); - - } - fn init_state(world: &mut World) -> Self::State { - ($($name::init_state(world),)*) - } - fn get_state(components: &Components) -> Option { - Some(($($name::get_state(components)?,)*)) - } - - fn matches_component_set(_state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - let ($($name,)*) = _state; - false $(|| $name::matches_component_set($name, _set_contains_id))* - } - } - - #[expect( - clippy::allow_attributes, - reason = "This is a tuple-related macro; as such the lints below may not always apply." - )] - #[allow( - non_snake_case, - reason = "The names of some variables are provided by the macro's caller, not by us." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." - )] - $(#[$meta])* - // SAFETY: defers to soundness of `$name: WorldQuery` impl - unsafe impl<$($name: QueryData),*> QueryData for AnyOf<($($name,)*)> { - const IS_READ_ONLY: bool = true $(&& $name::IS_READ_ONLY)*; - type ReadOnly = AnyOf<($($name::ReadOnly,)*)>; - type Item<'w, 's> = ($(Option<$name::Item<'w, 's>>,)*); - - fn shrink<'wlong: 'wshort, 'wshort, 's>(item: Self::Item<'wlong, 's>) -> Self::Item<'wshort, 's> { - let ($($name,)*) = item; - ($( - $name.map($name::shrink), - )*) - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow - ) -> Self::Item<'w, 's> { - let ($($name,)*) = _fetch; - let ($($state,)*) = _state; - ($( - // SAFETY: The invariants are required to be upheld by the caller. - $name.1.then(|| unsafe { $name::fetch($state, &mut $name.0, _entity, _table_row) }), - )*) - } - } - - $(#[$meta])* - /// SAFETY: each item in the tuple is read only - unsafe impl<$($name: ReadOnlyQueryData),*> ReadOnlyQueryData for AnyOf<($($name,)*)> {} - - #[expect( - clippy::allow_attributes, - reason = "This is a tuple-related macro; as such the lints below may not always apply." - )] - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." - )] - impl<$($name: ReleaseStateQueryData),*> ReleaseStateQueryData for AnyOf<($($name,)*)> { - fn release_state<'w>(($($item,)*): Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - ($($item.map(|$item| $name::release_state($item)),)*) - } - } - }; -} - -all_tuples!( - #[doc(fake_variadic)] - impl_tuple_query_data, - 0, - 15, - F, - i, - s -); -all_tuples!( - #[doc(fake_variadic)] - impl_anytuple_fetch, - 0, - 15, - F, - S, - i -); - -/// [`WorldQuery`] used to nullify queries by turning `Query` into `Query>` -/// -/// This will rarely be useful to consumers of `bevy_ecs`. -pub(crate) struct NopWorldQuery(PhantomData); - -/// SAFETY: -/// `update_component_access` does nothing. -/// This is sound because `fetch` does not access components. -unsafe impl WorldQuery for NopWorldQuery { - type Fetch<'w> = (); - type State = D::State; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - } - - #[inline(always)] - unsafe fn init_fetch( - _world: UnsafeWorldCell, - _state: &D::State, - _last_run: Tick, - _this_run: Tick, - ) { - } - - const IS_DENSE: bool = D::IS_DENSE; - - #[inline(always)] - unsafe fn set_archetype( - _fetch: &mut (), - _state: &D::State, - _archetype: &Archetype, - _tables: &Table, - ) { - } - - #[inline(always)] - unsafe fn set_table<'w>(_fetch: &mut (), _state: &D::State, _table: &Table) {} - - fn update_component_access(_state: &D::State, _access: &mut FilteredAccess) {} - - fn init_state(world: &mut World) -> Self::State { - D::init_state(world) - } - - fn get_state(components: &Components) -> Option { - D::get_state(components) - } - - fn matches_component_set( - state: &Self::State, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - D::matches_component_set(state, set_contains_id) - } -} - -/// SAFETY: `Self::ReadOnly` is `Self` -unsafe impl QueryData for NopWorldQuery { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = (); - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - _item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - } -} - -/// SAFETY: `NopFetch` never accesses any data -unsafe impl ReadOnlyQueryData for NopWorldQuery {} - -impl ReleaseStateQueryData for NopWorldQuery { - fn release_state<'w>(_item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> {} -} - -/// SAFETY: -/// `update_component_access` does nothing. -/// This is sound because `fetch` does not access components. -unsafe impl WorldQuery for PhantomData { - type Fetch<'w> = (); - - type State = (); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - } - - unsafe fn init_fetch<'w, 's>( - _world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> Self::Fetch<'w> { - } - - // `PhantomData` does not match any components, so all components it matches - // are stored in a Table (vacuous truth). - const IS_DENSE: bool = true; - - unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _archetype: &'w Archetype, - _table: &'w Table, - ) { - } - - unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _table: &'w Table, - ) { - } - - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - - fn init_state(_world: &mut World) -> Self::State {} - - fn get_state(_components: &Components) -> Option { - Some(()) - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -/// SAFETY: `Self::ReadOnly` is `Self` -unsafe impl QueryData for PhantomData { - const IS_READ_ONLY: bool = true; - type ReadOnly = Self; - type Item<'w, 's> = (); - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - _item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - } - - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - } -} - -/// SAFETY: `PhantomData` never accesses any world data. -unsafe impl ReadOnlyQueryData for PhantomData {} - -impl ReleaseStateQueryData for PhantomData { - fn release_state<'w>(_item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> {} -} - -/// A compile-time checked union of two different types that differs based on the -/// [`StorageType`] of a given component. -pub(super) union StorageSwitch { - /// The table variant. Requires the component to be a table component. - table: T, - /// The sparse set variant. Requires the component to be a sparse set component. - sparse_set: S, - _marker: PhantomData, -} - -impl StorageSwitch { - /// Creates a new [`StorageSwitch`] using the given closures to initialize - /// the variant corresponding to the component's [`StorageType`]. - pub fn new(table: impl FnOnce() -> T, sparse_set: impl FnOnce() -> S) -> Self { - match C::STORAGE_TYPE { - StorageType::Table => Self { table: table() }, - StorageType::SparseSet => Self { - sparse_set: sparse_set(), - }, - } - } - - /// Creates a new [`StorageSwitch`] using a table variant. - /// - /// # Panics - /// - /// This will panic on debug builds if `C` is not a table component. - /// - /// # Safety - /// - /// `C` must be a table component. - #[inline] - pub unsafe fn set_table(&mut self, table: T) { - match C::STORAGE_TYPE { - StorageType::Table => self.table = table, - _ => { - #[cfg(debug_assertions)] - unreachable!(); - #[cfg(not(debug_assertions))] - core::hint::unreachable_unchecked() - } - } - } - - /// Fetches the internal value from the variant that corresponds to the - /// component's [`StorageType`]. - pub fn extract(&self, table: impl FnOnce(T) -> R, sparse_set: impl FnOnce(S) -> R) -> R { - match C::STORAGE_TYPE { - StorageType::Table => table( - // SAFETY: C::STORAGE_TYPE == StorageType::Table - unsafe { self.table }, - ), - StorageType::SparseSet => sparse_set( - // SAFETY: C::STORAGE_TYPE == StorageType::SparseSet - unsafe { self.sparse_set }, - ), - } - } -} - -impl Clone for StorageSwitch { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for StorageSwitch {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::change_detection::DetectChanges; - use crate::system::{assert_is_system, Query}; - use bevy_ecs::prelude::Schedule; - use bevy_ecs_macros::QueryData; - - #[derive(Component)] - pub struct A; - - #[derive(Component)] - pub struct B; - - // Tests that each variant of struct can be used as a `WorldQuery`. - #[test] - fn world_query_struct_variants() { - #[derive(QueryData)] - pub struct NamedQuery { - id: Entity, - a: &'static A, - } - - #[derive(QueryData)] - pub struct TupleQuery(&'static A, &'static B); - - #[derive(QueryData)] - pub struct UnitQuery; - - fn my_system(_: Query<(NamedQuery, TupleQuery, UnitQuery)>) {} - - assert_is_system(my_system); - } - - // Compile test for https://github.com/bevyengine/bevy/pull/8030. - #[test] - fn world_query_phantom_data() { - #[derive(QueryData)] - pub struct IgnoredQuery { - id: Entity, - _marker: PhantomData, - } - - fn ignored_system(_: Query>) {} - - assert_is_system(ignored_system); - } - - #[test] - fn derive_release_state() { - struct NonReleaseQueryData; - - /// SAFETY: - /// `update_component_access` and `update_archetype_component_access` do nothing. - /// This is sound because `fetch` does not access components. - unsafe impl WorldQuery for NonReleaseQueryData { - type Fetch<'w> = (); - type State = (); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>( - _: Self::Fetch<'wlong>, - ) -> Self::Fetch<'wshort> { - } - - unsafe fn init_fetch<'w, 's>( - _world: UnsafeWorldCell<'w>, - _state: &'s Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> Self::Fetch<'w> { - } - - const IS_DENSE: bool = true; - - #[inline] - unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _archetype: &'w Archetype, - _table: &Table, - ) { - } - - #[inline] - unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s Self::State, - _table: &'w Table, - ) { - } - - fn update_component_access( - _state: &Self::State, - _access: &mut FilteredAccess, - ) { - } - - fn init_state(_world: &mut World) {} - - fn get_state(_components: &Components) -> Option<()> { - Some(()) - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } - } - - /// SAFETY: `Self` is the same as `Self::ReadOnly` - unsafe impl QueryData for NonReleaseQueryData { - type ReadOnly = Self; - const IS_READ_ONLY: bool = true; - - type Item<'w, 's> = (); - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - _item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - _state: &'s Self::State, - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w, 's> { - } - } - - /// SAFETY: access is read only - unsafe impl ReadOnlyQueryData for NonReleaseQueryData {} - - #[derive(QueryData)] - pub struct DerivedNonReleaseRead { - non_release: NonReleaseQueryData, - a: &'static A, - } - - #[derive(QueryData)] - #[query_data(mutable)] - pub struct DerivedNonReleaseMutable { - non_release: NonReleaseQueryData, - a: &'static mut A, - } - - #[derive(QueryData)] - pub struct DerivedReleaseRead { - a: &'static A, - } - - #[derive(QueryData)] - #[query_data(mutable)] - pub struct DerivedReleaseMutable { - a: &'static mut A, - } - - fn assert_is_release_state() {} - - assert_is_release_state::(); - assert_is_release_state::(); - } - - // Ensures that each field of a `WorldQuery` struct's read-only variant - // has the same visibility as its corresponding mutable field. - #[test] - fn read_only_field_visibility() { - mod private { - use super::*; - - #[derive(QueryData)] - #[query_data(mutable)] - pub struct D { - pub a: &'static mut A, - } - } - - let _ = private::DReadOnly { a: &A }; - - fn my_system(query: Query) { - for q in &query { - let _ = &q.a; - } - } - - assert_is_system(my_system); - } - - // Ensures that metadata types generated by the WorldQuery macro - // do not conflict with user-defined types. - // Regression test for https://github.com/bevyengine/bevy/issues/8010. - #[test] - fn world_query_metadata_collision() { - // The metadata types generated would be named `ClientState` and `ClientFetch`, - // but they should rename themselves to avoid conflicts. - #[derive(QueryData)] - pub struct Client { - pub state: &'static S, - pub fetch: &'static ClientFetch, - } - - pub trait ClientState: Component {} - - #[derive(Component)] - pub struct ClientFetch; - - #[derive(Component)] - pub struct C; - - impl ClientState for C {} - - fn client_system(_: Query>) {} - - assert_is_system(client_system); - } - - // Test that EntityRef::get_ref::() returns a Ref value with the correct - // ticks when the EntityRef was retrieved from a Query. - // See: https://github.com/bevyengine/bevy/issues/13735 - #[test] - fn test_entity_ref_query_with_ticks() { - #[derive(Component)] - pub struct C; - - fn system(query: Query) { - for entity_ref in &query { - if let Some(c) = entity_ref.get_ref::() { - if !c.is_added() { - panic!("Expected C to be added"); - } - } - } - } - - let mut world = World::new(); - let mut schedule = Schedule::default(); - schedule.add_systems(system); - world.spawn(C); - - // reset the change ticks - world.clear_trackers(); - - // we want EntityRef to use the change ticks of the system - schedule.run(&mut world); - } -} diff --git a/src/query/filter.rs b/src/query/filter.rs deleted file mode 100644 index f9f4861..0000000 --- a/src/query/filter.rs +++ /dev/null @@ -1,1274 +0,0 @@ -use crate::{ - archetype::Archetype, - component::{Component, ComponentId, Components, StorageType, Tick}, - entity::{Entities, Entity}, - query::{DebugCheckedUnwrap, FilteredAccess, StorageSwitch, WorldQuery}, - storage::{ComponentSparseSet, Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, World}, -}; -use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; -use bevy_utils::prelude::DebugName; -use core::{cell::UnsafeCell, marker::PhantomData}; -use variadics_please::all_tuples; - -/// Types that filter the results of a [`Query`]. -/// -/// There are many types that natively implement this trait: -/// - **Component filters.** -/// [`With`] and [`Without`] filters can be applied to check if the queried entity does or does not contain a particular component. -/// - **Change detection filters.** -/// [`Added`] and [`Changed`] filters can be applied to detect component changes to an entity. -/// - **Spawned filter.** -/// [`Spawned`] filter can be applied to check if the queried entity was spawned recently. -/// - **`QueryFilter` tuples.** -/// If every element of a tuple implements `QueryFilter`, then the tuple itself also implements the same trait. -/// This enables a single `Query` to filter over multiple conditions. -/// Due to the current lack of variadic generics in Rust, the trait has been implemented for tuples from 0 to 15 elements, -/// but nesting of tuples allows infinite `QueryFilter`s. -/// - **Filter disjunction operator.** -/// By default, tuples compose query filters in such a way that all conditions must be satisfied to generate a query item for a given entity. -/// Wrapping a tuple inside an [`Or`] operator will relax the requirement to just one condition. -/// -/// Implementing the trait manually can allow for a fundamentally new type of behavior. -/// -/// Query design can be easily structured by deriving `QueryFilter` for custom types. -/// Despite the added complexity, this approach has several advantages over using `QueryFilter` tuples. -/// The most relevant improvements are: -/// -/// - Reusability across multiple systems. -/// - Filters can be composed together to create a more complex filter. -/// -/// This trait can only be derived for structs if each field also implements `QueryFilter`. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::{query::QueryFilter, component::Component}; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # #[derive(Component)] -/// # struct ComponentB; -/// # #[derive(Component)] -/// # struct ComponentC; -/// # #[derive(Component)] -/// # struct ComponentD; -/// # #[derive(Component)] -/// # struct ComponentE; -/// # -/// #[derive(QueryFilter)] -/// struct MyFilter { -/// // Field names are not relevant, since they are never manually accessed. -/// with_a: With, -/// or_filter: Or<(With, Added)>, -/// generic_tuple: (With, Without

), -/// } -/// -/// fn my_system(query: Query>) { -/// // ... -/// } -/// # bevy_ecs::system::assert_is_system(my_system); -/// ``` -/// -/// [`Query`]: crate::system::Query -/// -/// # Safety -/// -/// The [`WorldQuery`] implementation must not take any mutable access. -/// This is the same safety requirement as [`ReadOnlyQueryData`](crate::query::ReadOnlyQueryData). -#[diagnostic::on_unimplemented( - message = "`{Self}` is not a valid `Query` filter", - label = "invalid `Query` filter", - note = "a `QueryFilter` typically uses a combination of `With` and `Without` statements" -)] -pub unsafe trait QueryFilter: WorldQuery { - /// Returns true if (and only if) this Filter relies strictly on archetypes to limit which - /// components are accessed by the Query. - /// - /// This enables optimizations for [`crate::query::QueryIter`] that rely on knowing exactly how - /// many elements are being iterated (such as `Iterator::collect()`). - /// - /// If this is `true`, then [`QueryFilter::filter_fetch`] must always return true. - const IS_ARCHETYPAL: bool; - - /// Returns true if the provided [`Entity`] and [`TableRow`] should be included in the query results. - /// If false, the entity will be skipped. - /// - /// Note that this is called after already restricting the matched [`Table`]s and [`Archetype`]s to the - /// ones that are compatible with the Filter's access. - /// - /// Implementors of this method will generally either have a trivial `true` body (required for archetypal filters), - /// or access the necessary data within this function to make the final decision on filter inclusion. - /// - /// # Safety - /// - /// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and - /// `table_row` must be in the range of the current table and archetype. - unsafe fn filter_fetch( - state: &Self::State, - fetch: &mut Self::Fetch<'_>, - entity: Entity, - table_row: TableRow, - ) -> bool; -} - -/// Filter that selects entities with a component `T`. -/// -/// This can be used in a [`Query`](crate::system::Query) if entities are required to have the -/// component `T` but you don't actually care about components value. -/// -/// This is the negation of [`Without`]. -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::query::With; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::system::Query; -/// # -/// # #[derive(Component)] -/// # struct IsBeautiful; -/// # #[derive(Component)] -/// # struct Name { name: &'static str }; -/// # -/// fn compliment_entity_system(query: Query<&Name, With>) { -/// for name in &query { -/// println!("{} is looking lovely today!", name.name); -/// } -/// } -/// # bevy_ecs::system::assert_is_system(compliment_entity_system); -/// ``` -pub struct With(PhantomData); - -/// SAFETY: -/// `update_component_access` does not add any accesses. -/// This is sound because [`QueryFilter::filter_fetch`] does not access any components. -/// `update_component_access` adds a `With` filter for `T`. -/// This is sound because `matches_component_set` returns whether the set contains the component. -unsafe impl WorldQuery for With { - type Fetch<'w> = (); - type State = ComponentId; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} - - #[inline] - unsafe fn init_fetch( - _world: UnsafeWorldCell, - _state: &ComponentId, - _last_run: Tick, - _this_run: Tick, - ) { - } - - const IS_DENSE: bool = { - match T::STORAGE_TYPE { - StorageType::Table => true, - StorageType::SparseSet => false, - } - }; - - #[inline] - unsafe fn set_archetype( - _fetch: &mut (), - _state: &ComponentId, - _archetype: &Archetype, - _table: &Table, - ) { - } - - #[inline] - unsafe fn set_table(_fetch: &mut (), _state: &ComponentId, _table: &Table) {} - - #[inline] - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { - access.and_with(id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set( - &id: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(id) - } -} - -// SAFETY: WorldQuery impl performs no access at all -unsafe impl QueryFilter for With { - const IS_ARCHETYPAL: bool = true; - - #[inline(always)] - unsafe fn filter_fetch( - _state: &Self::State, - _fetch: &mut Self::Fetch<'_>, - _entity: Entity, - _table_row: TableRow, - ) -> bool { - true - } -} - -/// Filter that selects entities without a component `T`. -/// -/// This is the negation of [`With`]. -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::query::Without; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::system::Query; -/// # -/// # #[derive(Component)] -/// # struct Permit; -/// # #[derive(Component)] -/// # struct Name { name: &'static str }; -/// # -/// fn no_permit_system(query: Query<&Name, Without>) { -/// for name in &query{ -/// println!("{} has no permit!", name.name); -/// } -/// } -/// # bevy_ecs::system::assert_is_system(no_permit_system); -/// ``` -pub struct Without(PhantomData); - -/// SAFETY: -/// `update_component_access` does not add any accesses. -/// This is sound because [`QueryFilter::filter_fetch`] does not access any components. -/// `update_component_access` adds a `Without` filter for `T`. -/// This is sound because `matches_component_set` returns whether the set does not contain the component. -unsafe impl WorldQuery for Without { - type Fetch<'w> = (); - type State = ComponentId; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} - - #[inline] - unsafe fn init_fetch( - _world: UnsafeWorldCell, - _state: &ComponentId, - _last_run: Tick, - _this_run: Tick, - ) { - } - - const IS_DENSE: bool = { - match T::STORAGE_TYPE { - StorageType::Table => true, - StorageType::SparseSet => false, - } - }; - - #[inline] - unsafe fn set_archetype( - _fetch: &mut (), - _state: &ComponentId, - _archetype: &Archetype, - _table: &Table, - ) { - } - - #[inline] - unsafe fn set_table(_fetch: &mut (), _state: &Self::State, _table: &Table) {} - - #[inline] - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { - access.and_without(id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set( - &id: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - !set_contains_id(id) - } -} - -// SAFETY: WorldQuery impl performs no access at all -unsafe impl QueryFilter for Without { - const IS_ARCHETYPAL: bool = true; - - #[inline(always)] - unsafe fn filter_fetch( - _state: &Self::State, - _fetch: &mut Self::Fetch<'_>, - _entity: Entity, - _table_row: TableRow, - ) -> bool { - true - } -} - -/// A filter that tests if any of the given filters apply. -/// -/// This is useful for example if a system with multiple components in a query only wants to run -/// when one or more of the components have changed. -/// -/// The `And` equivalent to this filter is a [`prim@tuple`] testing that all the contained filters -/// apply instead. -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::entity::Entity; -/// # use bevy_ecs::query::Changed; -/// # use bevy_ecs::query::Or; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::system::Query; -/// # -/// # #[derive(Component, Debug)] -/// # struct Color {}; -/// # #[derive(Component)] -/// # struct Node {}; -/// # -/// fn print_cool_entity_system(query: Query, Changed)>>) { -/// for entity in &query { -/// println!("Entity {} got a new style or color", entity); -/// } -/// } -/// # bevy_ecs::system::assert_is_system(print_cool_entity_system); -/// ``` -pub struct Or(PhantomData); - -#[doc(hidden)] -pub struct OrFetch<'w, T: WorldQuery> { - fetch: T::Fetch<'w>, - matches: bool, -} - -impl Clone for OrFetch<'_, T> { - fn clone(&self) -> Self { - Self { - fetch: self.fetch.clone(), - matches: self.matches, - } - } -} - -macro_rules! impl_or_query_filter { - ($(#[$meta:meta])* $(($filter: ident, $state: ident)),*) => { - $(#[$meta])* - #[expect( - clippy::allow_attributes, - reason = "This is a tuple-related macro; as such the lints below may not always apply." - )] - #[allow( - non_snake_case, - reason = "The names of some variables are provided by the macro's caller, not by us." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." - )] - /// SAFETY: - /// [`QueryFilter::filter_fetch`] accesses are a subset of the subqueries' accesses - /// This is sound because `update_component_access` adds accesses according to the implementations of all the subqueries. - /// `update_component_access` replace the filters with a disjunction where every element is a conjunction of the previous filters and the filters of one of the subqueries. - /// This is sound because `matches_component_set` returns a disjunction of the results of the subqueries' implementations. - unsafe impl<$($filter: QueryFilter),*> WorldQuery for Or<($($filter,)*)> { - type Fetch<'w> = ($(OrFetch<'w, $filter>,)*); - type State = ($($filter::State,)*); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - let ($($filter,)*) = fetch; - ($( - OrFetch { - fetch: $filter::shrink_fetch($filter.fetch), - matches: $filter.matches - }, - )*) - } - - const IS_DENSE: bool = true $(&& $filter::IS_DENSE)*; - - #[inline] - unsafe fn init_fetch<'w, 's>(world: UnsafeWorldCell<'w>, state: &'s Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> { - let ($($filter,)*) = state; - ($(OrFetch { - // SAFETY: The invariants are upheld by the caller. - fetch: unsafe { $filter::init_fetch(world, $filter, last_run, this_run) }, - matches: false, - },)*) - } - - #[inline] - unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w>, state: &'s Self::State, table: &'w Table) { - let ($($filter,)*) = fetch; - let ($($state,)*) = state; - $( - $filter.matches = $filter::matches_component_set($state, &|id| table.has_column(id)); - if $filter.matches { - // SAFETY: The invariants are upheld by the caller. - unsafe { $filter::set_table(&mut $filter.fetch, $state, table); } - } - )* - } - - #[inline] - unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w>, - state: &'s Self::State, - archetype: &'w Archetype, - table: &'w Table - ) { - let ($($filter,)*) = fetch; - let ($($state,)*) = &state; - $( - $filter.matches = $filter::matches_component_set($state, &|id| archetype.contains(id)); - if $filter.matches { - // SAFETY: The invariants are upheld by the caller. - unsafe { $filter::set_archetype(&mut $filter.fetch, $state, archetype, table); } - } - )* - } - - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - let ($($filter,)*) = state; - - let mut new_access = FilteredAccess::matches_nothing(); - - $( - // Create an intermediate because `access`'s value needs to be preserved - // for the next filter, and `_new_access` has to be modified only by `append_or` to it. - let mut intermediate = access.clone(); - $filter::update_component_access($filter, &mut intermediate); - new_access.append_or(&intermediate); - // Also extend the accesses required to compute the filter. This is required because - // otherwise a `Query<(), Or<(Changed,)>` won't conflict with `Query<&mut Foo>`. - new_access.extend_access(&intermediate); - )* - - // The required components remain the same as the original `access`. - new_access.required = core::mem::take(&mut access.required); - - *access = new_access; - } - - fn init_state(world: &mut World) -> Self::State { - ($($filter::init_state(world),)*) - } - - fn get_state(components: &Components) -> Option { - Some(($($filter::get_state(components)?,)*)) - } - - fn matches_component_set(state: &Self::State, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - let ($($filter,)*) = state; - false $(|| $filter::matches_component_set($filter, set_contains_id))* - } - } - - #[expect( - clippy::allow_attributes, - reason = "This is a tuple-related macro; as such the lints below may not always apply." - )] - #[allow( - non_snake_case, - reason = "The names of some variables are provided by the macro's caller, not by us." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - $(#[$meta])* - // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. - unsafe impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> { - const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; - - #[inline(always)] - unsafe fn filter_fetch( - state: &Self::State, - fetch: &mut Self::Fetch<'_>, - entity: Entity, - table_row: TableRow - ) -> bool { - let ($($state,)*) = state; - let ($($filter,)*) = fetch; - // SAFETY: The invariants are upheld by the caller. - false $(|| ($filter.matches && unsafe { $filter::filter_fetch($state, &mut $filter.fetch, entity, table_row) }))* - } - } - }; -} - -macro_rules! impl_tuple_query_filter { - ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { - #[expect( - clippy::allow_attributes, - reason = "This is a tuple-related macro; as such the lints below may not always apply." - )] - #[allow( - non_snake_case, - reason = "The names of some variables are provided by the macro's caller, not by us." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - $(#[$meta])* - // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. - unsafe impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) { - const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*; - - #[inline(always)] - unsafe fn filter_fetch( - state: &Self::State, - fetch: &mut Self::Fetch<'_>, - entity: Entity, - table_row: TableRow - ) -> bool { - let ($($state,)*) = state; - let ($($name,)*) = fetch; - // SAFETY: The invariants are upheld by the caller. - true $(&& unsafe { $name::filter_fetch($state, $name, entity, table_row) })* - } - } - - }; -} - -all_tuples!( - #[doc(fake_variadic)] - impl_tuple_query_filter, - 0, - 15, - F, - S -); -all_tuples!( - #[doc(fake_variadic)] - impl_or_query_filter, - 0, - 15, - F, - S -); - -/// Allows a query to contain entities with the component `T`, bypassing [`DefaultQueryFilters`]. -/// -/// [`DefaultQueryFilters`]: crate::entity_disabling::DefaultQueryFilters -pub struct Allows(PhantomData); - -/// SAFETY: -/// `update_component_access` does not add any accesses. -/// This is sound because [`QueryFilter::filter_fetch`] does not access any components. -/// `update_component_access` adds an archetypal filter for `T`. -/// This is sound because it doesn't affect the query -unsafe impl WorldQuery for Allows { - type Fetch<'w> = (); - type State = ComponentId; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} - - #[inline] - unsafe fn init_fetch(_: UnsafeWorldCell, _: &ComponentId, _: Tick, _: Tick) {} - - // Even if the component is sparse, this implementation doesn't do anything with it - const IS_DENSE: bool = true; - - #[inline] - unsafe fn set_archetype(_: &mut (), _: &ComponentId, _: &Archetype, _: &Table) {} - - #[inline] - unsafe fn set_table(_: &mut (), _: &ComponentId, _: &Table) {} - - #[inline] - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { - access.access_mut().add_archetypal(id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set(_: &ComponentId, _: &impl Fn(ComponentId) -> bool) -> bool { - // Allows always matches - true - } -} - -// SAFETY: WorldQuery impl performs no access at all -unsafe impl QueryFilter for Allows { - const IS_ARCHETYPAL: bool = true; - - #[inline(always)] - unsafe fn filter_fetch( - _: &Self::State, - _: &mut Self::Fetch<'_>, - _: Entity, - _: TableRow, - ) -> bool { - true - } -} - -/// A filter on a component that only retains results the first time after they have been added. -/// -/// A common use for this filter is one-time initialization. -/// -/// To retain all results without filtering but still check whether they were added after the -/// system last ran, use [`Ref`](crate::change_detection::Ref). -/// -/// **Note** that this includes changes that happened before the first time this `Query` was run. -/// -/// # Deferred -/// -/// Note, that entity modifications issued with [`Commands`](crate::system::Commands) -/// are visible only after deferred operations are applied, typically after the system -/// that queued them. -/// -/// # Time complexity -/// -/// `Added` is not [`ArchetypeFilter`], which practically means that -/// if the query (with `T` component filter) matches a million entities, -/// `Added` filter will iterate over all of them even if none of them were just added. -/// -/// For example, these two systems are roughly equivalent in terms of performance: -/// -/// ``` -/// # use bevy_ecs::change_detection::{DetectChanges, Ref}; -/// # use bevy_ecs::entity::Entity; -/// # use bevy_ecs::query::Added; -/// # use bevy_ecs::system::Query; -/// # use bevy_ecs_macros::Component; -/// # #[derive(Component)] -/// # struct MyComponent; -/// # #[derive(Component)] -/// # struct Transform; -/// -/// fn system1(q: Query<&MyComponent, Added>) { -/// for item in &q { /* component added */ } -/// } -/// -/// fn system2(q: Query<(&MyComponent, Ref)>) { -/// for item in &q { -/// if item.1.is_added() { /* component added */ } -/// } -/// } -/// ``` -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::query::Added; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::system::Query; -/// # -/// # #[derive(Component, Debug)] -/// # struct Name {}; -/// -/// fn print_add_name_component(query: Query<&Name, Added>) { -/// for name in &query { -/// println!("Named entity created: {:?}", name) -/// } -/// } -/// -/// # bevy_ecs::system::assert_is_system(print_add_name_component); -/// ``` -pub struct Added(PhantomData); - -#[doc(hidden)] -pub struct AddedFetch<'w, T: Component> { - ticks: StorageSwitch< - T, - // T::STORAGE_TYPE = StorageType::Table - Option>>, - // T::STORAGE_TYPE = StorageType::SparseSet - // Can be `None` when the component has never been inserted - Option<&'w ComponentSparseSet>, - >, - last_run: Tick, - this_run: Tick, -} - -impl Clone for AddedFetch<'_, T> { - fn clone(&self) -> Self { - Self { - ticks: self.ticks, - last_run: self.last_run, - this_run: self.this_run, - } - } -} - -/// SAFETY: -/// [`QueryFilter::filter_fetch`] accesses a single component in a readonly way. -/// This is sound because `update_component_access` adds read access for that component and panics when appropriate. -/// `update_component_access` adds a `With` filter for a component. -/// This is sound because `matches_component_set` returns whether the set contains that component. -unsafe impl WorldQuery for Added { - type Fetch<'w> = AddedFetch<'w, T>; - type State = ComponentId; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - #[inline] - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - &id: &'s ComponentId, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - Self::Fetch::<'w> { - ticks: StorageSwitch::new( - || None, - || { - // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_component_access`. - // Note that we do not actually access any components' ticks in this function, we just get a shared - // reference to the sparse set, which is used to access the components' ticks in `Self::fetch`. - unsafe { world.storages().sparse_sets.get(id) } - }, - ), - last_run, - this_run, - } - } - - const IS_DENSE: bool = { - match T::STORAGE_TYPE { - StorageType::Table => true, - StorageType::SparseSet => false, - } - }; - - #[inline] - unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w>, - component_id: &'s ComponentId, - _archetype: &'w Archetype, - table: &'w Table, - ) { - if Self::IS_DENSE { - // SAFETY: `set_archetype`'s safety rules are a super set of the `set_table`'s ones. - unsafe { - Self::set_table(fetch, component_id, table); - } - } - } - - #[inline] - unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w>, - &component_id: &'s ComponentId, - table: &'w Table, - ) { - let table_ticks = Some( - table - .get_added_ticks_slice_for(component_id) - .debug_checked_unwrap() - .into(), - ); - // SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table - unsafe { fetch.ticks.set_table(table_ticks) }; - } - - #[inline] - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { - if access.access().has_component_write(id) { - panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", DebugName::type_name::()); - } - access.add_component_read(id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set( - &id: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(id) - } -} - -// SAFETY: WorldQuery impl performs only read access on ticks -unsafe impl QueryFilter for Added { - const IS_ARCHETYPAL: bool = false; - #[inline(always)] - unsafe fn filter_fetch( - _state: &Self::State, - fetch: &mut Self::Fetch<'_>, - entity: Entity, - table_row: TableRow, - ) -> bool { - // SAFETY: The invariants are upheld by the caller. - fetch.ticks.extract( - |table| { - // SAFETY: set_table was previously called - let table = unsafe { table.debug_checked_unwrap() }; - // SAFETY: The caller ensures `table_row` is in range. - let tick = unsafe { table.get(table_row.index()) }; - - tick.deref().is_newer_than(fetch.last_run, fetch.this_run) - }, - |sparse_set| { - // SAFETY: The caller ensures `entity` is in range. - let tick = unsafe { - sparse_set - .debug_checked_unwrap() - .get_added_tick(entity) - .debug_checked_unwrap() - }; - - tick.deref().is_newer_than(fetch.last_run, fetch.this_run) - }, - ) - } -} - -/// A filter on a component that only retains results the first time after they have been added or mutably dereferenced. -/// -/// A common use for this filter is avoiding redundant work when values have not changed. -/// -/// **Note** that simply *mutably dereferencing* a component is considered a change ([`DerefMut`](std::ops::DerefMut)). -/// Bevy does not compare components to their previous values. -/// -/// To retain all results without filtering but still check whether they were changed after the -/// system last ran, use [`Ref`](crate::change_detection::Ref). -/// -/// **Note** that this includes changes that happened before the first time this `Query` was run. -/// -/// # Deferred -/// -/// Note, that entity modifications issued with [`Commands`](crate::system::Commands) -/// (like entity creation or entity component addition or removal) are visible only -/// after deferred operations are applied, typically after the system that queued them. -/// -/// # Time complexity -/// -/// `Changed` is not [`ArchetypeFilter`], which practically means that -/// if query (with `T` component filter) matches million entities, -/// `Changed` filter will iterate over all of them even if none of them were changed. -/// -/// For example, these two systems are roughly equivalent in terms of performance: -/// -/// ``` -/// # use bevy_ecs::change_detection::DetectChanges; -/// # use bevy_ecs::entity::Entity; -/// # use bevy_ecs::query::Changed; -/// # use bevy_ecs::system::Query; -/// # use bevy_ecs::world::Ref; -/// # use bevy_ecs_macros::Component; -/// # #[derive(Component)] -/// # struct MyComponent; -/// # #[derive(Component)] -/// # struct Transform; -/// -/// fn system1(q: Query<&MyComponent, Changed>) { -/// for item in &q { /* component changed */ } -/// } -/// -/// fn system2(q: Query<(&MyComponent, Ref)>) { -/// for item in &q { -/// if item.1.is_changed() { /* component changed */ } -/// } -/// } -/// ``` -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::query::Changed; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::system::Query; -/// # -/// # #[derive(Component, Debug)] -/// # struct Name {}; -/// # #[derive(Component)] -/// # struct Transform {}; -/// -/// fn print_moving_objects_system(query: Query<&Name, Changed>) { -/// for name in &query { -/// println!("Entity Moved: {:?}", name); -/// } -/// } -/// -/// # bevy_ecs::system::assert_is_system(print_moving_objects_system); -/// ``` -pub struct Changed(PhantomData); - -#[doc(hidden)] -pub struct ChangedFetch<'w, T: Component> { - ticks: StorageSwitch< - T, - Option>>, - // Can be `None` when the component has never been inserted - Option<&'w ComponentSparseSet>, - >, - last_run: Tick, - this_run: Tick, -} - -impl Clone for ChangedFetch<'_, T> { - fn clone(&self) -> Self { - Self { - ticks: self.ticks, - last_run: self.last_run, - this_run: self.this_run, - } - } -} - -/// SAFETY: -/// `fetch` accesses a single component in a readonly way. -/// This is sound because `update_component_access` add read access for that component and panics when appropriate. -/// `update_component_access` adds a `With` filter for a component. -/// This is sound because `matches_component_set` returns whether the set contains that component. -unsafe impl WorldQuery for Changed { - type Fetch<'w> = ChangedFetch<'w, T>; - type State = ComponentId; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - #[inline] - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - &id: &'s ComponentId, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - Self::Fetch::<'w> { - ticks: StorageSwitch::new( - || None, - || { - // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_component_access`. - // Note that we do not actually access any components' ticks in this function, we just get a shared - // reference to the sparse set, which is used to access the components' ticks in `Self::fetch`. - unsafe { world.storages().sparse_sets.get(id) } - }, - ), - last_run, - this_run, - } - } - - const IS_DENSE: bool = { - match T::STORAGE_TYPE { - StorageType::Table => true, - StorageType::SparseSet => false, - } - }; - - #[inline] - unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w>, - component_id: &'s ComponentId, - _archetype: &'w Archetype, - table: &'w Table, - ) { - if Self::IS_DENSE { - // SAFETY: `set_archetype`'s safety rules are a super set of the `set_table`'s ones. - unsafe { - Self::set_table(fetch, component_id, table); - } - } - } - - #[inline] - unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w>, - &component_id: &'s ComponentId, - table: &'w Table, - ) { - let table_ticks = Some( - table - .get_changed_ticks_slice_for(component_id) - .debug_checked_unwrap() - .into(), - ); - // SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table - unsafe { fetch.ticks.set_table(table_ticks) }; - } - - #[inline] - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { - if access.access().has_component_write(id) { - panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", DebugName::type_name::()); - } - access.add_component_read(id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set( - &id: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(id) - } -} - -// SAFETY: WorldQuery impl performs only read access on ticks -unsafe impl QueryFilter for Changed { - const IS_ARCHETYPAL: bool = false; - - #[inline(always)] - unsafe fn filter_fetch( - _state: &Self::State, - fetch: &mut Self::Fetch<'_>, - entity: Entity, - table_row: TableRow, - ) -> bool { - // SAFETY: The invariants are upheld by the caller. - fetch.ticks.extract( - |table| { - // SAFETY: set_table was previously called - let table = unsafe { table.debug_checked_unwrap() }; - // SAFETY: The caller ensures `table_row` is in range. - let tick = unsafe { table.get(table_row.index()) }; - - tick.deref().is_newer_than(fetch.last_run, fetch.this_run) - }, - |sparse_set| { - // SAFETY: The caller ensures `entity` is in range. - let tick = unsafe { - sparse_set - .debug_checked_unwrap() - .get_changed_tick(entity) - .debug_checked_unwrap() - }; - - tick.deref().is_newer_than(fetch.last_run, fetch.this_run) - }, - ) - } -} - -/// A filter that only retains results the first time after the entity has been spawned. -/// -/// A common use for this filter is one-time initialization. -/// -/// To retain all results without filtering but still check whether they were spawned after the -/// system last ran, use [`SpawnDetails`](crate::query::SpawnDetails) instead. -/// -/// **Note** that this includes entities that spawned before the first time this Query was run. -/// -/// # Deferred -/// -/// Note, that entity spawns issued with [`Commands`](crate::system::Commands) -/// are visible only after deferred operations are applied, typically after the -/// system that queued them. -/// -/// # Time complexity -/// -/// `Spawned` is not [`ArchetypeFilter`], which practically means that if query matches million -/// entities, `Spawned` filter will iterate over all of them even if none of them were spawned. -/// -/// For example, these two systems are roughly equivalent in terms of performance: -/// -/// ``` -/// # use bevy_ecs::entity::Entity; -/// # use bevy_ecs::system::Query; -/// # use bevy_ecs::query::Spawned; -/// # use bevy_ecs::query::SpawnDetails; -/// -/// fn system1(query: Query) { -/// for entity in &query { /* entity spawned */ } -/// } -/// -/// fn system2(query: Query<(Entity, SpawnDetails)>) { -/// for (entity, spawned) in &query { -/// if spawned.is_spawned() { /* entity spawned */ } -/// } -/// } -/// ``` -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::query::Spawned; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::system::Query; -/// # -/// # #[derive(Component, Debug)] -/// # struct Name {}; -/// -/// fn print_spawning_entities(query: Query<&Name, Spawned>) { -/// for name in &query { -/// println!("Entity spawned: {:?}", name); -/// } -/// } -/// -/// # bevy_ecs::system::assert_is_system(print_spawning_entities); -/// ``` -pub struct Spawned; - -#[doc(hidden)] -#[derive(Clone)] -pub struct SpawnedFetch<'w> { - entities: &'w Entities, - last_run: Tick, - this_run: Tick, -} - -// SAFETY: WorldQuery impl accesses no components or component ticks -unsafe impl WorldQuery for Spawned { - type Fetch<'w> = SpawnedFetch<'w>; - type State = (); - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - fetch - } - - #[inline] - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - _state: &'s (), - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - SpawnedFetch { - entities: world.entities(), - last_run, - this_run, - } - } - - const IS_DENSE: bool = true; - - #[inline] - unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w>, - _state: &'s (), - _archetype: &'w Archetype, - _table: &'w Table, - ) { - } - - #[inline] - unsafe fn set_table<'w, 's>(_fetch: &mut Self::Fetch<'w>, _state: &'s (), _table: &'w Table) {} - - #[inline] - fn update_component_access(_state: &(), _access: &mut FilteredAccess) {} - - fn init_state(_world: &mut World) {} - - fn get_state(_components: &Components) -> Option<()> { - Some(()) - } - - fn matches_component_set(_state: &(), _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - true - } -} - -// SAFETY: WorldQuery impl accesses no components or component ticks -unsafe impl QueryFilter for Spawned { - const IS_ARCHETYPAL: bool = false; - - #[inline(always)] - unsafe fn filter_fetch( - _state: &Self::State, - fetch: &mut Self::Fetch<'_>, - entity: Entity, - _table_row: TableRow, - ) -> bool { - // SAFETY: only living entities are queried - let spawned = unsafe { - fetch - .entities - .entity_get_spawned_or_despawned_unchecked(entity) - .1 - }; - spawned.is_newer_than(fetch.last_run, fetch.this_run) - } -} - -/// A marker trait to indicate that the filter works at an archetype level. -/// -/// This is needed to implement [`ExactSizeIterator`] for -/// [`QueryIter`](crate::query::QueryIter) that contains archetype-level filters. -/// -/// The trait must only be implemented for filters where its corresponding [`QueryFilter::IS_ARCHETYPAL`] -/// is [`prim@true`]. As such, only the [`With`] and [`Without`] filters can implement the trait. -/// [Tuples](prim@tuple) and [`Or`] filters are automatically implemented with the trait only if its containing types -/// also implement the same trait. -/// -/// [`Added`], [`Changed`] and [`Spawned`] work with entities, and therefore are not archetypal. As such -/// they do not implement [`ArchetypeFilter`]. -#[diagnostic::on_unimplemented( - message = "`{Self}` is not a valid `Query` filter based on archetype information", - label = "invalid `Query` filter", - note = "an `ArchetypeFilter` typically uses a combination of `With` and `Without` statements" -)] -pub trait ArchetypeFilter: QueryFilter {} - -impl ArchetypeFilter for With {} - -impl ArchetypeFilter for Without {} - -macro_rules! impl_archetype_filter_tuple { - ($(#[$meta:meta])* $($filter: ident),*) => { - $(#[$meta])* - impl<$($filter: ArchetypeFilter),*> ArchetypeFilter for ($($filter,)*) {} - }; -} - -macro_rules! impl_archetype_or_filter_tuple { - ($(#[$meta:meta])* $($filter: ident),*) => { - $(#[$meta])* - impl<$($filter: ArchetypeFilter),*> ArchetypeFilter for Or<($($filter,)*)> {} - }; -} - -all_tuples!( - #[doc(fake_variadic)] - impl_archetype_filter_tuple, - 0, - 15, - F -); - -all_tuples!( - #[doc(fake_variadic)] - impl_archetype_or_filter_tuple, - 0, - 15, - F -); diff --git a/src/query/iter.rs b/src/query/iter.rs deleted file mode 100644 index eb49204..0000000 --- a/src/query/iter.rs +++ /dev/null @@ -1,3012 +0,0 @@ -use super::{QueryData, QueryFilter, ReadOnlyQueryData}; -use crate::{ - archetype::{Archetype, ArchetypeEntity, Archetypes}, - bundle::Bundle, - component::Tick, - entity::{ContainsEntity, Entities, Entity, EntityEquivalent, EntitySet, EntitySetIterator}, - query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, StorageId}, - storage::{Table, TableRow, Tables}, - world::{ - unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, - FilteredEntityMut, FilteredEntityRef, - }, -}; -use alloc::vec::Vec; -use core::{ - cmp::Ordering, - fmt::{self, Debug, Formatter}, - iter::FusedIterator, - mem::MaybeUninit, - ops::Range, -}; -use nonmax::NonMaxU32; - -/// An [`Iterator`] over query results of a [`Query`](crate::system::Query). -/// -/// This struct is created by the [`Query::iter`](crate::system::Query::iter) and -/// [`Query::iter_mut`](crate::system::Query::iter_mut) methods. -pub struct QueryIter<'w, 's, D: QueryData, F: QueryFilter> { - world: UnsafeWorldCell<'w>, - tables: &'w Tables, - archetypes: &'w Archetypes, - query_state: &'s QueryState, - cursor: QueryIterationCursor<'w, 's, D, F>, -} - -impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { - /// # Safety - /// - `world` must have permission to access any of the components registered in `query_state`. - /// - `world` must be the same one used to initialize `query_state`. - pub(crate) unsafe fn new( - world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, - last_run: Tick, - this_run: Tick, - ) -> Self { - QueryIter { - world, - query_state, - // SAFETY: We only access table data that has been registered in `query_state`. - tables: unsafe { &world.storages().tables }, - archetypes: world.archetypes(), - // SAFETY: The invariants are upheld by the caller. - cursor: unsafe { QueryIterationCursor::init(world, query_state, last_run, this_run) }, - } - } - - /// Creates a new separate iterator yielding the same remaining items of the current one. - /// Advancing the new iterator will not advance the original one, which will resume at the - /// point it was left at. - /// - /// Differently from [`remaining_mut`](QueryIter::remaining_mut) the new iterator does not - /// borrow from the original one. However it can only be called from an iterator over read only - /// items. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct ComponentA; - /// - /// fn combinations(query: Query<&ComponentA>) { - /// let mut iter = query.iter(); - /// while let Some(a) = iter.next() { - /// for b in iter.remaining() { - /// // Check every combination (a, b) - /// } - /// } - /// } - /// ``` - pub fn remaining(&self) -> QueryIter<'w, 's, D, F> - where - D: ReadOnlyQueryData, - { - QueryIter { - world: self.world, - tables: self.tables, - archetypes: self.archetypes, - query_state: self.query_state, - cursor: self.cursor.clone(), - } - } - - /// Creates a new separate iterator yielding the same remaining items of the current one. - /// Advancing the new iterator will not advance the original one, which will resume at the - /// point it was left at. - /// - /// This method can be called on iterators over mutable items. However the original iterator - /// will be borrowed while the new iterator exists and will thus not be usable in that timespan. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct ComponentA; - /// - /// fn combinations(mut query: Query<&mut ComponentA>) { - /// let mut iter = query.iter_mut(); - /// while let Some(a) = iter.next() { - /// for b in iter.remaining_mut() { - /// // Check every combination (a, b) - /// } - /// } - /// } - /// ``` - pub fn remaining_mut(&mut self) -> QueryIter<'_, 's, D, F> { - QueryIter { - world: self.world, - tables: self.tables, - archetypes: self.archetypes, - query_state: self.query_state, - cursor: self.cursor.reborrow(), - } - } - - /// Executes the equivalent of [`Iterator::fold`] over a contiguous segment - /// from a storage. - /// - /// # Safety - /// - `range` must be in `[0, storage::entity_count)` or None. - #[inline] - pub(super) unsafe fn fold_over_storage_range( - &mut self, - mut accum: B, - func: &mut Func, - storage: StorageId, - range: Option>, - ) -> B - where - Func: FnMut(B, D::Item<'w, 's>) -> B, - { - if self.cursor.is_dense { - // SAFETY: `self.cursor.is_dense` is true, so storage ids are guaranteed to be table ids. - let table_id = unsafe { storage.table_id }; - // SAFETY: Matched table IDs are guaranteed to still exist. - let table = unsafe { self.tables.get(table_id).debug_checked_unwrap() }; - - let range = range.unwrap_or(0..table.entity_count()); - accum = - // SAFETY: - // - The fetched table matches both D and F - // - caller ensures `range` is within `[0, table.entity_count)` - // - The if block ensures that the query iteration is dense - unsafe { self.fold_over_table_range(accum, func, table, range) }; - } else { - // SAFETY: `self.cursor.is_dense` is false, so storage ids are guaranteed to be archetype ids. - let archetype_id = unsafe { storage.archetype_id }; - // SAFETY: Matched archetype IDs are guaranteed to still exist. - let archetype = unsafe { self.archetypes.get(archetype_id).debug_checked_unwrap() }; - // SAFETY: Matched table IDs are guaranteed to still exist. - let table = unsafe { self.tables.get(archetype.table_id()).debug_checked_unwrap() }; - - let range = range.unwrap_or(0..archetype.len()); - - // When an archetype and its table have equal entity counts, dense iteration can be safely used. - // this leverages cache locality to optimize performance. - if table.entity_count() == archetype.len() { - accum = - // SAFETY: - // - The fetched archetype matches both D and F - // - The provided archetype and its' table have the same length. - // - caller ensures `range` is within `[0, archetype.len)` - // - The if block ensures that the query iteration is not dense. - unsafe { self.fold_over_dense_archetype_range(accum, func, archetype,range) }; - } else { - accum = - // SAFETY: - // - The fetched archetype matches both D and F - // - caller ensures `range` is within `[0, archetype.len)` - // - The if block ensures that the query iteration is not dense. - unsafe { self.fold_over_archetype_range(accum, func, archetype,range) }; - } - } - accum - } - - /// Executes the equivalent of [`Iterator::fold`] over a contiguous segment - /// from a table. - /// - /// # Safety - /// - all `rows` must be in `[0, table.entity_count)`. - /// - `table` must match D and F - /// - The query iteration must be dense (i.e. `self.query_state.is_dense` must be true). - #[inline] - pub(super) unsafe fn fold_over_table_range( - &mut self, - mut accum: B, - func: &mut Func, - table: &'w Table, - rows: Range, - ) -> B - where - Func: FnMut(B, D::Item<'w, 's>) -> B, - { - if table.is_empty() { - return accum; - } - - D::set_table(&mut self.cursor.fetch, &self.query_state.fetch_state, table); - F::set_table( - &mut self.cursor.filter, - &self.query_state.filter_state, - table, - ); - - let entities = table.entities(); - for row in rows { - // SAFETY: Caller assures `row` in range of the current archetype. - let entity = unsafe { entities.get_unchecked(row as usize) }; - // SAFETY: This is from an exclusive range, so it can't be max. - let row = unsafe { TableRow::new(NonMaxU32::new_unchecked(row)) }; - - // SAFETY: set_table was called prior. - // Caller assures `row` in range of the current archetype. - let fetched = unsafe { - !F::filter_fetch( - &self.query_state.filter_state, - &mut self.cursor.filter, - *entity, - row, - ) - }; - if fetched { - continue; - } - - // SAFETY: set_table was called prior. - // Caller assures `row` in range of the current archetype. - let item = D::fetch( - &self.query_state.fetch_state, - &mut self.cursor.fetch, - *entity, - row, - ); - - accum = func(accum, item); - } - accum - } - - /// Executes the equivalent of [`Iterator::fold`] over a contiguous segment - /// from an archetype. - /// - /// # Safety - /// - all `indices` must be in `[0, archetype.len())`. - /// - `archetype` must match D and F - /// - The query iteration must not be dense (i.e. `self.query_state.is_dense` must be false). - #[inline] - pub(super) unsafe fn fold_over_archetype_range( - &mut self, - mut accum: B, - func: &mut Func, - archetype: &'w Archetype, - indices: Range, - ) -> B - where - Func: FnMut(B, D::Item<'w, 's>) -> B, - { - if archetype.is_empty() { - return accum; - } - let table = self.tables.get(archetype.table_id()).debug_checked_unwrap(); - D::set_archetype( - &mut self.cursor.fetch, - &self.query_state.fetch_state, - archetype, - table, - ); - F::set_archetype( - &mut self.cursor.filter, - &self.query_state.filter_state, - archetype, - table, - ); - - let entities = archetype.entities(); - for index in indices { - // SAFETY: Caller assures `index` in range of the current archetype. - let archetype_entity = unsafe { entities.get_unchecked(index as usize) }; - - // SAFETY: set_archetype was called prior. - // Caller assures `index` in range of the current archetype. - let fetched = unsafe { - !F::filter_fetch( - &self.query_state.filter_state, - &mut self.cursor.filter, - archetype_entity.id(), - archetype_entity.table_row(), - ) - }; - if fetched { - continue; - } - - // SAFETY: set_archetype was called prior, `index` is an archetype index in range of the current archetype - // Caller assures `index` in range of the current archetype. - let item = unsafe { - D::fetch( - &self.query_state.fetch_state, - &mut self.cursor.fetch, - archetype_entity.id(), - archetype_entity.table_row(), - ) - }; - - accum = func(accum, item); - } - accum - } - - /// Executes the equivalent of [`Iterator::fold`] over a contiguous segment - /// from an archetype which has the same entity count as its table. - /// - /// # Safety - /// - all `indices` must be in `[0, archetype.len())`. - /// - `archetype` must match D and F - /// - `archetype` must have the same length as its table. - /// - The query iteration must not be dense (i.e. `self.query_state.is_dense` must be false). - #[inline] - pub(super) unsafe fn fold_over_dense_archetype_range( - &mut self, - mut accum: B, - func: &mut Func, - archetype: &'w Archetype, - rows: Range, - ) -> B - where - Func: FnMut(B, D::Item<'w, 's>) -> B, - { - if archetype.is_empty() { - return accum; - } - let table = self.tables.get(archetype.table_id()).debug_checked_unwrap(); - debug_assert!( - archetype.len() == table.entity_count(), - "archetype and its table must have the same length. " - ); - - D::set_archetype( - &mut self.cursor.fetch, - &self.query_state.fetch_state, - archetype, - table, - ); - F::set_archetype( - &mut self.cursor.filter, - &self.query_state.filter_state, - archetype, - table, - ); - let entities = table.entities(); - for row in rows { - // SAFETY: Caller assures `row` in range of the current archetype. - let entity = unsafe { *entities.get_unchecked(row as usize) }; - // SAFETY: This is from an exclusive range, so it can't be max. - let row = unsafe { TableRow::new(NonMaxU32::new_unchecked(row)) }; - - // SAFETY: set_table was called prior. - // Caller assures `row` in range of the current archetype. - let filter_matched = unsafe { - F::filter_fetch( - &self.query_state.filter_state, - &mut self.cursor.filter, - entity, - row, - ) - }; - if !filter_matched { - continue; - } - - // SAFETY: set_table was called prior. - // Caller assures `row` in range of the current archetype. - let item = D::fetch( - &self.query_state.fetch_state, - &mut self.cursor.fetch, - entity, - row, - ); - - accum = func(accum, item); - } - accum - } - - /// Sorts all query items into a new iterator, using the query lens as a key. - /// - /// This sort is stable (i.e., does not reorder equal elements). - /// - /// This uses [`slice::sort`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// # Panics - /// - /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. - /// - /// # Examples - /// ```rust - /// # use bevy_ecs::prelude::*; - /// # use std::{ops::{Deref, DerefMut}, iter::Sum}; - /// # - /// # #[derive(Component)] - /// # struct PartMarker; - /// # - /// # #[derive(Component, PartialEq, Eq, PartialOrd, Ord)] - /// # struct PartIndex(usize); - /// # - /// # #[derive(Component, Clone, Copy)] - /// # struct PartValue(f32); - /// # - /// # impl Deref for PartValue { - /// # type Target = f32; - /// # - /// # fn deref(&self) -> &Self::Target { - /// # &self.0 - /// # } - /// # } - /// # - /// # #[derive(Component)] - /// # struct ParentValue(f32); - /// # - /// # impl Deref for ParentValue { - /// # type Target = f32; - /// # - /// # fn deref(&self) -> &Self::Target { - /// # &self.0 - /// # } - /// # } - /// # - /// # impl DerefMut for ParentValue { - /// # fn deref_mut(&mut self) -> &mut Self::Target { - /// # &mut self.0 - /// # } - /// # } - /// # - /// # #[derive(Component, Debug, PartialEq, Eq, PartialOrd, Ord)] - /// # struct Length(usize); - /// # - /// # #[derive(Component, Debug, PartialEq, Eq, PartialOrd, Ord)] - /// # struct Width(usize); - /// # - /// # #[derive(Component, Debug, PartialEq, Eq, PartialOrd, Ord)] - /// # struct Height(usize); - /// # - /// # #[derive(Component, PartialEq, Eq, PartialOrd, Ord)] - /// # struct ParentEntity(Entity); - /// # - /// # #[derive(Component, Clone, Copy)] - /// # struct ChildPartCount(usize); - /// # - /// # impl Deref for ChildPartCount { - /// # type Target = usize; - /// # - /// # fn deref(&self) -> &Self::Target { - /// # &self.0 - /// # } - /// # } - /// # let mut world = World::new(); - /// // We can ensure that a query always returns in the same order. - /// fn system_1(query: Query<(Entity, &PartIndex)>) { - /// let parts: Vec<(Entity, &PartIndex)> = query.iter().sort::<&PartIndex>().collect(); - /// } - /// - /// // We can freely rearrange query components in the key. - /// fn system_2(query: Query<(&Length, &Width, &Height), With>) { - /// for (length, width, height) in query.iter().sort::<(&Height, &Length, &Width)>() { - /// println!("height: {height:?}, width: {width:?}, length: {length:?}") - /// } - /// } - /// - /// // We can sort by Entity without including it in the original Query. - /// // Here, we match iteration orders between query iterators. - /// fn system_3( - /// part_query: Query<(&PartValue, &ParentEntity)>, - /// mut parent_query: Query<(&ChildPartCount, &mut ParentValue)>, - /// ) { - /// let part_values = &mut part_query - /// .into_iter() - /// .sort::<&ParentEntity>() - /// .map(|(&value, parent_entity)| *value); - /// - /// for (&child_count, mut parent_value) in parent_query.iter_mut().sort::() { - /// **parent_value = part_values.take(*child_count).sum(); - /// } - /// } - /// # - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems((system_1, system_2, system_3)); - /// # schedule.run(&mut world); - /// ``` - pub fn sort( - self, - ) -> QuerySortedIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > - where - for<'lw, 'ls> L::Item<'lw, 'ls>: Ord, - { - self.sort_impl::(|keyed_query| keyed_query.sort()) - } - - /// Sorts all query items into a new iterator, using the query lens as a key. - /// - /// This sort is unstable (i.e., may reorder equal elements). - /// - /// This uses [`slice::sort_unstable`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes].. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// # Panics - /// - /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. - /// - /// # Example - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut world = World::new(); - /// # - /// # #[derive(Component)] - /// # struct PartMarker; - /// # - /// #[derive(Component, PartialEq, Eq, PartialOrd, Ord)] - /// enum Flying { - /// Enabled, - /// Disabled - /// }; - /// - /// // We perform an unstable sort by a Component with few values. - /// fn system_1(query: Query<&Flying, With>) { - /// let part_values: Vec<&Flying> = query.iter().sort_unstable::<&Flying>().collect(); - /// } - /// # - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems((system_1)); - /// # schedule.run(&mut world); - /// ``` - pub fn sort_unstable( - self, - ) -> QuerySortedIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > - where - for<'lw, 'ls> L::Item<'lw, 'ls>: Ord, - { - self.sort_impl::(|keyed_query| keyed_query.sort_unstable()) - } - - /// Sorts all query items into a new iterator with a comparator function over the query lens. - /// - /// This sort is stable (i.e., does not reorder equal elements). - /// - /// This uses [`slice::sort_by`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// # Panics - /// - /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. - /// - /// # Example - /// ``` - /// # use bevy_ecs::prelude::*; - /// # use std::ops::Deref; - /// # - /// # impl Deref for PartValue { - /// # type Target = f32; - /// # - /// # fn deref(&self) -> &Self::Target { - /// # &self.0 - /// # } - /// # } - /// # - /// # let mut world = World::new(); - /// # - /// #[derive(Component)] - /// struct PartValue(f32); - /// - /// // We can use a cmp function on components do not implement Ord. - /// fn system_1(query: Query<&PartValue>) { - /// // Sort part values according to `f32::total_comp`. - /// let part_values: Vec<&PartValue> = query - /// .iter() - /// .sort_by::<&PartValue>(|value_1, value_2| value_1.total_cmp(*value_2)) - /// .collect(); - /// } - /// # - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems((system_1)); - /// # schedule.run(&mut world); - /// ``` - pub fn sort_by( - self, - mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering, - ) -> QuerySortedIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > { - self.sort_impl::(move |keyed_query| { - keyed_query.sort_by(|(key_1, _), (key_2, _)| compare(key_1, key_2)); - }) - } - - /// Sorts all query items into a new iterator with a comparator function over the query lens. - /// - /// This sort is unstable (i.e., may reorder equal elements). - /// - /// This uses [`slice::sort_unstable_by`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// # Panics - /// - /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. - pub fn sort_unstable_by( - self, - mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering, - ) -> QuerySortedIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > { - self.sort_impl::(move |keyed_query| { - keyed_query.sort_unstable_by(|(key_1, _), (key_2, _)| compare(key_1, key_2)); - }) - } - - /// Sorts all query items into a new iterator with a key extraction function over the query lens. - /// - /// This sort is stable (i.e., does not reorder equal elements). - /// - /// This uses [`slice::sort_by_key`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// # Panics - /// - /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. - /// - /// # Example - /// ``` - /// # use bevy_ecs::prelude::*; - /// # use std::ops::Deref; - /// # - /// # #[derive(Component)] - /// # struct PartMarker; - /// # - /// # impl Deref for PartValue { - /// # type Target = f32; - /// # - /// # fn deref(&self) -> &Self::Target { - /// # &self.0 - /// # } - /// # } - /// # - /// # let mut world = World::new(); - /// # - /// #[derive(Component)] - /// struct AvailableMarker; - /// - /// #[derive(Component, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] - /// enum Rarity { - /// Common, - /// Rare, - /// Epic, - /// Legendary - /// }; - /// - /// #[derive(Component)] - /// struct PartValue(f32); - /// - /// // We can sort with the internals of components that do not implement Ord. - /// fn system_1(query: Query<(Entity, &PartValue)>) { - /// // Sort by the sines of the part values. - /// let parts: Vec<(Entity, &PartValue)> = query - /// .iter() - /// .sort_by_key::<&PartValue, _>(|value| value.sin() as usize) - /// .collect(); - /// } - /// - /// // We can define our own custom comparison functions over an EntityRef. - /// fn system_2(query: Query>) { - /// // Sort by whether parts are available and their rarity. - /// // We want the available legendaries to come first, so we reverse the iterator. - /// let parts: Vec = query.iter() - /// .sort_by_key::(|entity_ref| { - /// ( - /// entity_ref.contains::(), - /// entity_ref.get::().copied() - /// ) - /// }) - /// .rev() - /// .collect(); - /// } - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems((system_1, system_2)); - /// # schedule.run(&mut world); - /// ``` - pub fn sort_by_key( - self, - mut f: impl FnMut(&L::Item<'_, '_>) -> K, - ) -> QuerySortedIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > - where - K: Ord, - { - self.sort_impl::(move |keyed_query| keyed_query.sort_by_key(|(lens, _)| f(lens))) - } - - /// Sorts all query items into a new iterator with a key extraction function over the query lens. - /// - /// This sort is unstable (i.e., may reorder equal elements). - /// - /// This uses [`slice::sort_unstable_by_key`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// # Panics - /// - /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. - pub fn sort_unstable_by_key( - self, - mut f: impl FnMut(&L::Item<'_, '_>) -> K, - ) -> QuerySortedIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > - where - K: Ord, - { - self.sort_impl::(move |keyed_query| { - keyed_query.sort_unstable_by_key(|(lens, _)| f(lens)); - }) - } - - /// Sort all query items into a new iterator with a key extraction function over the query lens. - /// - /// This sort is stable (i.e., does not reorder equal elements). - /// - /// This uses [`slice::sort_by_cached_key`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// # Panics - /// - /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. - pub fn sort_by_cached_key( - self, - mut f: impl FnMut(&L::Item<'_, '_>) -> K, - ) -> QuerySortedIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > - where - K: Ord, - { - self.sort_impl::(move |keyed_query| keyed_query.sort_by_cached_key(|(lens, _)| f(lens))) - } - - /// Shared implementation for the various `sort` methods. - /// This uses the lens to collect the items for sorting, but delegates the actual sorting to the provided closure. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// # Panics - /// - /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. - fn sort_impl( - self, - f: impl FnOnce(&mut Vec<(L::Item<'_, '_>, NeutralOrd)>), - ) -> QuerySortedIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > { - // On the first successful iteration of `QueryIterationCursor`, `archetype_entities` or `table_entities` - // will be set to a non-zero value. The correctness of this method relies on this. - // I.e. this sort method will execute if and only if `next` on `QueryIterationCursor` of a - // non-empty `QueryIter` has not yet been called. When empty, this sort method will not panic. - if !self.cursor.archetype_entities.is_empty() || !self.cursor.table_entities.is_empty() { - panic!("it is not valid to call sort() after next()") - } - - let world = self.world; - - let query_lens_state = self.query_state.transmute_filtered::<(L, Entity), F>(world); - - // SAFETY: - // `self.world` has permission to access the required components. - // The original query iter has not been iterated on, so no items are aliased from it. - // `QueryIter::new` ensures `world` is the same one used to initialize `query_state`. - let query_lens = unsafe { query_lens_state.query_unchecked_manual(world) }.into_iter(); - let mut keyed_query: Vec<_> = query_lens - .map(|(key, entity)| (key, NeutralOrd(entity))) - .collect(); - f(&mut keyed_query); - let entity_iter = keyed_query - .into_iter() - .map(|(.., entity)| entity.0) - .collect::>() - .into_iter(); - // SAFETY: - // `self.world` has permission to access the required components. - // Each lens query item is dropped before the respective actual query item is accessed. - unsafe { - QuerySortedIter::new( - world, - self.query_state, - entity_iter, - world.last_change_tick(), - world.change_tick(), - ) - } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> Iterator for QueryIter<'w, 's, D, F> { - type Item = D::Item<'w, 's>; - - #[inline(always)] - fn next(&mut self) -> Option { - // SAFETY: - // `tables` and `archetypes` belong to the same world that the cursor was initialized for. - // `query_state` is the state that was passed to `QueryIterationCursor::init`. - unsafe { - self.cursor - .next(self.tables, self.archetypes, self.query_state) - } - } - - fn size_hint(&self) -> (usize, Option) { - let max_size = self.cursor.max_remaining(self.tables, self.archetypes); - let archetype_query = F::IS_ARCHETYPAL; - let min_size = if archetype_query { max_size } else { 0 }; - (min_size as usize, Some(max_size as usize)) - } - - #[inline] - fn fold(mut self, init: B, mut func: Func) -> B - where - Func: FnMut(B, Self::Item) -> B, - { - let mut accum = init; - // Empty any remaining uniterated values from the current table/archetype - while self.cursor.current_row != self.cursor.current_len { - let Some(item) = self.next() else { break }; - accum = func(accum, item); - } - - for id in self.cursor.storage_id_iter.clone().copied() { - // SAFETY: - // - The range(None) is equivalent to [0, storage.entity_count) - accum = unsafe { self.fold_over_storage_range(accum, &mut func, id, None) }; - } - accum - } -} - -// This is correct as [`QueryIter`] always returns `None` once exhausted. -impl<'w, 's, D: QueryData, F: QueryFilter> FusedIterator for QueryIter<'w, 's, D, F> {} - -// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, Entity, F> {} - -// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, EntityRef<'_>, F> {} - -// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, EntityMut<'_>, F> {} - -// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator - for QueryIter<'w, 's, FilteredEntityRef<'_>, F> -{ -} - -// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator - for QueryIter<'w, 's, FilteredEntityMut<'_>, F> -{ -} - -// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator - for QueryIter<'w, 's, EntityRefExcept<'_, B>, F> -{ -} - -// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator - for QueryIter<'w, 's, EntityMutExcept<'_, B>, F> -{ -} - -impl<'w, 's, D: QueryData, F: QueryFilter> Debug for QueryIter<'w, 's, D, F> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("QueryIter").finish() - } -} - -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter> Clone for QueryIter<'w, 's, D, F> { - fn clone(&self) -> Self { - self.remaining() - } -} - -/// An [`Iterator`] over sorted query results of a [`Query`](crate::system::Query). -/// -/// This struct is created by the [`QueryIter::sort`], [`QueryIter::sort_unstable`], -/// [`QueryIter::sort_by`], [`QueryIter::sort_unstable_by`], [`QueryIter::sort_by_key`], -/// [`QueryIter::sort_unstable_by_key`], and [`QueryIter::sort_by_cached_key`] methods. -pub struct QuerySortedIter<'w, 's, D: QueryData, F: QueryFilter, I> -where - I: Iterator, -{ - entity_iter: I, - entities: &'w Entities, - tables: &'w Tables, - archetypes: &'w Archetypes, - fetch: D::Fetch<'w>, - query_state: &'s QueryState, -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> QuerySortedIter<'w, 's, D, F, I> -where - I: Iterator, -{ - /// # Safety - /// - `world` must have permission to access any of the components registered in `query_state`. - /// - `world` must be the same one used to initialize `query_state`. - /// - `entity_list` must only contain unique entities or be empty. - pub(crate) unsafe fn new>( - world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, - entity_list: EntityList, - last_run: Tick, - this_run: Tick, - ) -> QuerySortedIter<'w, 's, D, F, I> { - let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); - QuerySortedIter { - query_state, - entities: world.entities(), - archetypes: world.archetypes(), - // SAFETY: We only access table data that has been registered in `query_state`. - // This means `world` has permission to access the data we use. - tables: &world.storages().tables, - fetch, - entity_iter: entity_list.into_iter(), - } - } - - /// # Safety - /// `entity` must stem from `self.entity_iter`, and not have been passed before. - #[inline(always)] - unsafe fn fetch_next(&mut self, entity: Entity) -> D::Item<'w, 's> { - let (location, archetype, table); - // SAFETY: - // `tables` and `archetypes` belong to the same world that the [`QueryIter`] - // was initialized for. - unsafe { - location = self.entities.get(entity).debug_checked_unwrap(); - archetype = self - .archetypes - .get(location.archetype_id) - .debug_checked_unwrap(); - table = self.tables.get(location.table_id).debug_checked_unwrap(); - } - - // SAFETY: `archetype` is from the world that `fetch` was created for, - // `fetch_state` is the state that `fetch` was initialized with - unsafe { - D::set_archetype( - &mut self.fetch, - &self.query_state.fetch_state, - archetype, - table, - ); - } - - // The entity list has already been filtered by the query lens, so we forego filtering here. - // SAFETY: - // - set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype - // - fetch is only called once for each entity. - unsafe { - D::fetch( - &self.query_state.fetch_state, - &mut self.fetch, - entity, - location.table_row, - ) - } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Iterator - for QuerySortedIter<'w, 's, D, F, I> -where - I: Iterator, -{ - type Item = D::Item<'w, 's>; - - #[inline(always)] - fn next(&mut self) -> Option { - let entity = self.entity_iter.next()?; - // SAFETY: `entity` is passed from `entity_iter` the first time. - unsafe { self.fetch_next(entity).into() } - } - - fn size_hint(&self) -> (usize, Option) { - self.entity_iter.size_hint() - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> DoubleEndedIterator - for QuerySortedIter<'w, 's, D, F, I> -where - I: DoubleEndedIterator, -{ - #[inline(always)] - fn next_back(&mut self) -> Option { - let entity = self.entity_iter.next_back()?; - // SAFETY: `entity` is passed from `entity_iter` the first time. - unsafe { self.fetch_next(entity).into() } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> ExactSizeIterator - for QuerySortedIter<'w, 's, D, F, I> -where - I: ExactSizeIterator, -{ -} - -// This is correct as [`QuerySortedIter`] returns `None` once exhausted if `entity_iter` does. -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> FusedIterator - for QuerySortedIter<'w, 's, D, F, I> -where - I: FusedIterator, -{ -} - -// SAFETY: -// `I` stems from a collected and sorted `EntitySetIterator` ([`QueryIter`]). -// Fetching unique entities maintains uniqueness. -unsafe impl<'w, 's, F: QueryFilter, I: Iterator> EntitySetIterator - for QuerySortedIter<'w, 's, Entity, F, I> -{ -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug - for QuerySortedIter<'w, 's, D, F, I> -{ - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("QuerySortedIter").finish() - } -} - -/// An [`Iterator`] over the query items generated from an iterator of [`Entity`]s. -/// -/// Items are returned in the order of the provided iterator. -/// Entities that don't match the query are skipped. -/// -/// This struct is created by the [`Query::iter_many`](crate::system::Query::iter_many) and [`Query::iter_many_mut`](crate::system::Query::iter_many_mut) methods. -pub struct QueryManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> -{ - world: UnsafeWorldCell<'w>, - entity_iter: I, - entities: &'w Entities, - tables: &'w Tables, - archetypes: &'w Archetypes, - fetch: D::Fetch<'w>, - filter: F::Fetch<'w>, - query_state: &'s QueryState, -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> - QueryManyIter<'w, 's, D, F, I> -{ - /// # Safety - /// - `world` must have permission to access any of the components registered in `query_state`. - /// - `world` must be the same one used to initialize `query_state`. - pub(crate) unsafe fn new>( - world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, - entity_list: EntityList, - last_run: Tick, - this_run: Tick, - ) -> QueryManyIter<'w, 's, D, F, I> { - let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); - let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run); - QueryManyIter { - world, - query_state, - entities: world.entities(), - archetypes: world.archetypes(), - // SAFETY: We only access table data that has been registered in `query_state`. - // This means `world` has permission to access the data we use. - tables: &world.storages().tables, - fetch, - filter, - entity_iter: entity_list.into_iter(), - } - } - - /// # Safety - /// All arguments must stem from the same valid `QueryManyIter`. - /// - /// The lifetime here is not restrictive enough for Fetch with &mut access, - /// as calling `fetch_next_aliased_unchecked` multiple times can produce multiple - /// references to the same component, leading to unique reference aliasing. - /// - /// It is always safe for shared access. - #[inline(always)] - unsafe fn fetch_next_aliased_unchecked( - entity_iter: impl Iterator, - entities: &'w Entities, - tables: &'w Tables, - archetypes: &'w Archetypes, - fetch: &mut D::Fetch<'w>, - filter: &mut F::Fetch<'w>, - query_state: &'s QueryState, - ) -> Option> { - for entity_borrow in entity_iter { - let entity = entity_borrow.entity(); - let Some(location) = entities.get(entity) else { - continue; - }; - - if !query_state - .matched_archetypes - .contains(location.archetype_id.index()) - { - continue; - } - - let archetype = archetypes.get(location.archetype_id).debug_checked_unwrap(); - let table = tables.get(location.table_id).debug_checked_unwrap(); - - // SAFETY: `archetype` is from the world that `fetch/filter` were created for, - // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with - unsafe { - D::set_archetype(fetch, &query_state.fetch_state, archetype, table); - } - // SAFETY: `table` is from the world that `fetch/filter` were created for, - // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with - unsafe { - F::set_archetype(filter, &query_state.filter_state, archetype, table); - } - - // SAFETY: set_archetype was called prior. - // `location.archetype_row` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d - if unsafe { - F::filter_fetch( - &query_state.filter_state, - filter, - entity, - location.table_row, - ) - } { - // SAFETY: - // - set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype - // - fetch is only called once for each entity. - return Some(unsafe { - D::fetch(&query_state.fetch_state, fetch, entity, location.table_row) - }); - } - } - None - } - - /// Get next result from the query - #[inline(always)] - pub fn fetch_next(&mut self) -> Option> { - // SAFETY: - // All arguments stem from self. - // We are limiting the returned reference to self, - // making sure this method cannot be called multiple times without getting rid - // of any previously returned unique references first, thus preventing aliasing. - unsafe { - Self::fetch_next_aliased_unchecked( - &mut self.entity_iter, - self.entities, - self.tables, - self.archetypes, - &mut self.fetch, - &mut self.filter, - self.query_state, - ) - .map(D::shrink) - } - } - - /// Sorts all query items into a new iterator, using the query lens as a key. - /// - /// This sort is stable (i.e., does not reorder equal elements). - /// - /// This uses [`slice::sort`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// Unlike the sort methods on [`QueryIter`], this does NOT panic if `next`/`fetch_next` has been - /// called on [`QueryManyIter`] before. - /// - /// # Examples - /// ```rust - /// # use bevy_ecs::prelude::*; - /// # use std::{ops::{Deref, DerefMut}, iter::Sum}; - /// # - /// # #[derive(Component)] - /// # struct PartMarker; - /// # - /// # #[derive(Component, PartialEq, Eq, PartialOrd, Ord)] - /// # struct PartIndex(usize); - /// # - /// # #[derive(Component, Clone, Copy)] - /// # struct PartValue(usize); - /// # - /// # impl Deref for PartValue { - /// # type Target = usize; - /// # - /// # fn deref(&self) -> &Self::Target { - /// # &self.0 - /// # } - /// # } - /// # - /// # impl DerefMut for PartValue { - /// # fn deref_mut(&mut self) -> &mut Self::Target { - /// # &mut self.0 - /// # } - /// # } - /// # - /// # #[derive(Component, Debug, PartialEq, Eq, PartialOrd, Ord)] - /// # struct Length(usize); - /// # - /// # #[derive(Component, Debug, PartialEq, Eq, PartialOrd, Ord)] - /// # struct Width(usize); - /// # - /// # #[derive(Component, Debug, PartialEq, Eq, PartialOrd, Ord)] - /// # struct Height(usize); - /// # - /// # #[derive(Component, PartialEq, Eq, PartialOrd, Ord)] - /// # struct ParentEntity(Entity); - /// # - /// # let mut world = World::new(); - /// // We can ensure that a query always returns in the same order. - /// fn system_1(query: Query<(Entity, &PartIndex)>) { - /// # let entity_list: Vec = Vec::new(); - /// let parts: Vec<(Entity, &PartIndex)> = query.iter_many(entity_list).sort::<&PartIndex>().collect(); - /// } - /// - /// // We can freely rearrange query components in the key. - /// fn system_2(query: Query<(&Length, &Width, &Height), With>) { - /// # let entity_list: Vec = Vec::new(); - /// for (length, width, height) in query.iter_many(entity_list).sort::<(&Height, &Length, &Width)>() { - /// println!("height: {height:?}, width: {width:?}, length: {length:?}") - /// } - /// } - /// - /// // You can use `fetch_next_back` to obtain mutable references in reverse order. - /// fn system_3( - /// mut query: Query<&mut PartValue>, - /// ) { - /// # let entity_list: Vec = Vec::new(); - /// // We need to collect the internal iterator before iterating mutably - /// let mut parent_query_iter = query.iter_many_mut(entity_list) - /// .sort::(); - /// - /// let mut scratch_value = 0; - /// while let Some(mut part_value) = parent_query_iter.fetch_next_back() - /// { - /// // some order-dependent operation, here bitwise XOR - /// **part_value ^= scratch_value; - /// scratch_value = **part_value; - /// } - /// } - /// # - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems((system_1, system_2, system_3)); - /// # schedule.run(&mut world); - /// ``` - pub fn sort( - self, - ) -> QuerySortedManyIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > - where - for<'lw, 'ls> L::Item<'lw, 'ls>: Ord, - { - self.sort_impl::(|keyed_query| keyed_query.sort()) - } - - /// Sorts all query items into a new iterator, using the query lens as a key. - /// - /// This sort is unstable (i.e., may reorder equal elements). - /// - /// This uses [`slice::sort_unstable`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes].. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// Unlike the sort methods on [`QueryIter`], this does NOT panic if `next`/`fetch_next` has been - /// called on [`QueryManyIter`] before. - /// - /// # Example - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut world = World::new(); - /// # - /// # #[derive(Component)] - /// # struct PartMarker; - /// # - /// # let entity_list: Vec = Vec::new(); - /// #[derive(Component, PartialEq, Eq, PartialOrd, Ord)] - /// enum Flying { - /// Enabled, - /// Disabled - /// }; - /// - /// // We perform an unstable sort by a Component with few values. - /// fn system_1(query: Query<&Flying, With>) { - /// # let entity_list: Vec = Vec::new(); - /// let part_values: Vec<&Flying> = query.iter_many(entity_list).sort_unstable::<&Flying>().collect(); - /// } - /// # - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems((system_1)); - /// # schedule.run(&mut world); - /// ``` - pub fn sort_unstable( - self, - ) -> QuerySortedManyIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > - where - for<'lw, 'ls> L::Item<'lw, 'ls>: Ord, - { - self.sort_impl::(|keyed_query| keyed_query.sort_unstable()) - } - - /// Sorts all query items into a new iterator with a comparator function over the query lens. - /// - /// This sort is stable (i.e., does not reorder equal elements). - /// - /// This uses [`slice::sort_by`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// Unlike the sort methods on [`QueryIter`], this does NOT panic if `next`/`fetch_next` has been - /// called on [`QueryManyIter`] before. - /// - /// # Example - /// ``` - /// # use bevy_ecs::prelude::*; - /// # use std::ops::Deref; - /// # - /// # impl Deref for PartValue { - /// # type Target = f32; - /// # - /// # fn deref(&self) -> &Self::Target { - /// # &self.0 - /// # } - /// # } - /// # - /// # let mut world = World::new(); - /// # let entity_list: Vec = Vec::new(); - /// # - /// #[derive(Component)] - /// struct PartValue(f32); - /// - /// // We can use a cmp function on components do not implement Ord. - /// fn system_1(query: Query<&PartValue>) { - /// # let entity_list: Vec = Vec::new(); - /// // Sort part values according to `f32::total_comp`. - /// let part_values: Vec<&PartValue> = query - /// .iter_many(entity_list) - /// .sort_by::<&PartValue>(|value_1, value_2| value_1.total_cmp(*value_2)) - /// .collect(); - /// } - /// # - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems((system_1)); - /// # schedule.run(&mut world); - /// ``` - pub fn sort_by( - self, - mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering, - ) -> QuerySortedManyIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > { - self.sort_impl::(move |keyed_query| { - keyed_query.sort_by(|(key_1, _), (key_2, _)| compare(key_1, key_2)); - }) - } - - /// Sorts all query items into a new iterator with a comparator function over the query lens. - /// - /// This sort is unstable (i.e., may reorder equal elements). - /// - /// This uses [`slice::sort_unstable_by`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// Unlike the sort methods on [`QueryIter`], this does NOT panic if `next`/`fetch_next` has been - /// called on [`QueryManyIter`] before. - pub fn sort_unstable_by( - self, - mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering, - ) -> QuerySortedManyIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > { - self.sort_impl::(move |keyed_query| { - keyed_query.sort_unstable_by(|(key_1, _), (key_2, _)| compare(key_1, key_2)); - }) - } - - /// Sorts all query items into a new iterator with a key extraction function over the query lens. - /// - /// This sort is stable (i.e., does not reorder equal elements). - /// - /// This uses [`slice::sort_by_key`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// Unlike the sort methods on [`QueryIter`], this does NOT panic if `next`/`fetch_next` has been - /// called on [`QueryManyIter`] before. - /// - /// # Example - /// ``` - /// # use bevy_ecs::prelude::*; - /// # use std::ops::Deref; - /// # - /// # #[derive(Component)] - /// # struct PartMarker; - /// # - /// # impl Deref for PartValue { - /// # type Target = f32; - /// # - /// # fn deref(&self) -> &Self::Target { - /// # &self.0 - /// # } - /// # } - /// # - /// # let mut world = World::new(); - /// # let entity_list: Vec = Vec::new(); - /// # - /// #[derive(Component)] - /// struct AvailableMarker; - /// - /// #[derive(Component, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] - /// enum Rarity { - /// Common, - /// Rare, - /// Epic, - /// Legendary - /// }; - /// - /// #[derive(Component)] - /// struct PartValue(f32); - /// - /// // We can sort with the internals of components that do not implement Ord. - /// fn system_1(query: Query<(Entity, &PartValue)>) { - /// # let entity_list: Vec = Vec::new(); - /// // Sort by the sines of the part values. - /// let parts: Vec<(Entity, &PartValue)> = query - /// .iter_many(entity_list) - /// .sort_by_key::<&PartValue, _>(|value| value.sin() as usize) - /// .collect(); - /// } - /// - /// // We can define our own custom comparison functions over an EntityRef. - /// fn system_2(query: Query>) { - /// # let entity_list: Vec = Vec::new(); - /// // Sort by whether parts are available and their rarity. - /// // We want the available legendaries to come first, so we reverse the iterator. - /// let parts: Vec = query.iter_many(entity_list) - /// .sort_by_key::(|entity_ref| { - /// ( - /// entity_ref.contains::(), - // entity_ref.get::().copied() - /// ) - /// }) - /// .rev() - /// .collect(); - /// } - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems((system_1, system_2)); - /// # schedule.run(&mut world); - /// ``` - pub fn sort_by_key( - self, - mut f: impl FnMut(&L::Item<'_, '_>) -> K, - ) -> QuerySortedManyIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > - where - K: Ord, - { - self.sort_impl::(move |keyed_query| keyed_query.sort_by_key(|(lens, _)| f(lens))) - } - - /// Sorts all query items into a new iterator with a key extraction function over the query lens. - /// - /// This sort is unstable (i.e., may reorder equal elements). - /// - /// This uses [`slice::sort_unstable_by_key`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// Unlike the sort methods on [`QueryIter`], this does NOT panic if `next`/`fetch_next` has been - /// called on [`QueryManyIter`] before. - pub fn sort_unstable_by_key( - self, - mut f: impl FnMut(&L::Item<'_, '_>) -> K, - ) -> QuerySortedManyIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > - where - K: Ord, - { - self.sort_impl::(move |keyed_query| { - keyed_query.sort_unstable_by_key(|(lens, _)| f(lens)); - }) - } - - /// Sort all query items into a new iterator with a key extraction function over the query lens. - /// - /// This sort is stable (i.e., does not reorder equal elements). - /// - /// This uses [`slice::sort_by_cached_key`] internally. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// Unlike the sort methods on [`QueryIter`], this does NOT panic if `next`/`fetch_next` has been - /// called on [`QueryManyIter`] before. - pub fn sort_by_cached_key( - self, - mut f: impl FnMut(&L::Item<'_, '_>) -> K, - ) -> QuerySortedManyIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > - where - K: Ord, - { - self.sort_impl::(move |keyed_query| keyed_query.sort_by_cached_key(|(lens, _)| f(lens))) - } - - /// Shared implementation for the various `sort` methods. - /// This uses the lens to collect the items for sorting, but delegates the actual sorting to the provided closure. - /// - /// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens). - /// This includes the allowed parameter type changes listed under [allowed transmutes]. - /// However, the lens uses the filter of the original query when present. - /// - /// The sort is not cached across system runs. - /// - /// [allowed transmutes]: crate::system::Query#allowed-transmutes - /// - /// Unlike the sort methods on [`QueryIter`], this does NOT panic if `next`/`fetch_next` has been - /// called on [`QueryManyIter`] before. - fn sort_impl( - self, - f: impl FnOnce(&mut Vec<(L::Item<'_, '_>, NeutralOrd)>), - ) -> QuerySortedManyIter< - 'w, - 's, - D, - F, - impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, - > { - let world = self.world; - - let query_lens_state = self.query_state.transmute_filtered::<(L, Entity), F>(world); - - // SAFETY: - // `self.world` has permission to access the required components. - // The original query iter has not been iterated on, so no items are aliased from it. - // `QueryIter::new` ensures `world` is the same one used to initialize `query_state`. - let query_lens = unsafe { query_lens_state.query_unchecked_manual(world) } - .iter_many_inner(self.entity_iter); - let mut keyed_query: Vec<_> = query_lens - .map(|(key, entity)| (key, NeutralOrd(entity))) - .collect(); - f(&mut keyed_query); - // Re-collect into a `Vec` to eagerly drop the lens items. - // They must be dropped before `fetch_next` is called since they may alias - // with other data items if there are duplicate entities in `entity_iter`. - let entity_iter = keyed_query - .into_iter() - .map(|(.., entity)| entity.0) - .collect::>() - .into_iter(); - // SAFETY: - // `self.world` has permission to access the required components. - // Each lens query item is dropped before the respective actual query item is accessed. - unsafe { - QuerySortedManyIter::new( - world, - self.query_state, - entity_iter, - world.last_change_tick(), - world.change_tick(), - ) - } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator> - QueryManyIter<'w, 's, D, F, I> -{ - /// Get next result from the back of the query - #[inline(always)] - pub fn fetch_next_back(&mut self) -> Option> { - // SAFETY: - // All arguments stem from self. - // We are limiting the returned reference to self, - // making sure this method cannot be called multiple times without getting rid - // of any previously returned unique references first, thus preventing aliasing. - unsafe { - Self::fetch_next_aliased_unchecked( - self.entity_iter.by_ref().rev(), - self.entities, - self.tables, - self.archetypes, - &mut self.fetch, - &mut self.filter, - self.query_state, - ) - .map(D::shrink) - } - } -} - -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator> Iterator - for QueryManyIter<'w, 's, D, F, I> -{ - type Item = D::Item<'w, 's>; - - #[inline(always)] - fn next(&mut self) -> Option { - // SAFETY: - // All arguments stem from self. - // It is safe to alias for ReadOnlyWorldQuery. - unsafe { - Self::fetch_next_aliased_unchecked( - &mut self.entity_iter, - self.entities, - self.tables, - self.archetypes, - &mut self.fetch, - &mut self.filter, - self.query_state, - ) - } - } - - fn size_hint(&self) -> (usize, Option) { - let (_, max_size) = self.entity_iter.size_hint(); - (0, max_size) - } -} - -impl< - 'w, - 's, - D: ReadOnlyQueryData, - F: QueryFilter, - I: DoubleEndedIterator, - > DoubleEndedIterator for QueryManyIter<'w, 's, D, F, I> -{ - #[inline(always)] - fn next_back(&mut self) -> Option { - // SAFETY: - // All arguments stem from self. - // It is safe to alias for ReadOnlyWorldQuery. - unsafe { - Self::fetch_next_aliased_unchecked( - self.entity_iter.by_ref().rev(), - self.entities, - self.tables, - self.archetypes, - &mut self.fetch, - &mut self.filter, - self.query_state, - ) - } - } -} - -// This is correct as [`QueryManyIter`] always returns `None` once exhausted. -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator> - FusedIterator for QueryManyIter<'w, 's, D, F, I> -{ -} - -// SAFETY: Fetching unique entities maintains uniqueness. -unsafe impl<'w, 's, F: QueryFilter, I: EntitySetIterator> EntitySetIterator - for QueryManyIter<'w, 's, Entity, F, I> -{ -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug - for QueryManyIter<'w, 's, D, F, I> -{ - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("QueryManyIter").finish() - } -} - -/// An [`Iterator`] over the query items generated from an iterator of unique [`Entity`]s. -/// -/// Items are returned in the order of the provided iterator. -/// Entities that don't match the query are skipped. -/// -/// In contrast with [`QueryManyIter`], this allows for mutable iteration without a [`fetch_next`] method. -/// -/// This struct is created by the [`iter_many_unique`] and [`iter_many_unique_mut`] methods on [`Query`]. -/// -/// [`fetch_next`]: QueryManyIter::fetch_next -/// [`iter_many_unique`]: crate::system::Query::iter_many -/// [`iter_many_unique_mut`]: crate::system::Query::iter_many_mut -/// [`Query`]: crate::system::Query -pub struct QueryManyUniqueIter<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator>( - QueryManyIter<'w, 's, D, F, I>, -); - -impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> - QueryManyUniqueIter<'w, 's, D, F, I> -{ - /// # Safety - /// - `world` must have permission to access any of the components registered in `query_state`. - /// - `world` must be the same one used to initialize `query_state`. - pub(crate) unsafe fn new>( - world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, - entity_list: EntityList, - last_run: Tick, - this_run: Tick, - ) -> QueryManyUniqueIter<'w, 's, D, F, I> { - QueryManyUniqueIter(QueryManyIter::new( - world, - query_state, - entity_list, - last_run, - this_run, - )) - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> Iterator - for QueryManyUniqueIter<'w, 's, D, F, I> -{ - type Item = D::Item<'w, 's>; - - #[inline(always)] - fn next(&mut self) -> Option { - // SAFETY: Entities are guaranteed to be unique, thus do not alias. - unsafe { - QueryManyIter::<'w, 's, D, F, I>::fetch_next_aliased_unchecked( - &mut self.0.entity_iter, - self.0.entities, - self.0.tables, - self.0.archetypes, - &mut self.0.fetch, - &mut self.0.filter, - self.0.query_state, - ) - } - } - - fn size_hint(&self) -> (usize, Option) { - let (_, max_size) = self.0.entity_iter.size_hint(); - (0, max_size) - } -} - -// This is correct as [`QueryManyIter`] always returns `None` once exhausted. -impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> FusedIterator - for QueryManyUniqueIter<'w, 's, D, F, I> -{ -} - -// SAFETY: Fetching unique entities maintains uniqueness. -unsafe impl<'w, 's, F: QueryFilter, I: EntitySetIterator> EntitySetIterator - for QueryManyUniqueIter<'w, 's, Entity, F, I> -{ -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> Debug - for QueryManyUniqueIter<'w, 's, D, F, I> -{ - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("QueryManyUniqueIter").finish() - } -} - -/// An [`Iterator`] over sorted query results of a [`QueryManyIter`]. -/// -/// This struct is created by the [`sort`](QueryManyIter), [`sort_unstable`](QueryManyIter), -/// [`sort_by`](QueryManyIter), [`sort_unstable_by`](QueryManyIter), [`sort_by_key`](QueryManyIter), -/// [`sort_unstable_by_key`](QueryManyIter), and [`sort_by_cached_key`](QueryManyIter) methods of [`QueryManyIter`]. -pub struct QuerySortedManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> { - entity_iter: I, - entities: &'w Entities, - tables: &'w Tables, - archetypes: &'w Archetypes, - fetch: D::Fetch<'w>, - query_state: &'s QueryState, -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> - QuerySortedManyIter<'w, 's, D, F, I> -{ - /// # Safety - /// - `world` must have permission to access any of the components registered in `query_state`. - /// - `world` must be the same one used to initialize `query_state`. - /// - `entity_list` must only contain unique entities or be empty. - pub(crate) unsafe fn new>( - world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, - entity_list: EntityList, - last_run: Tick, - this_run: Tick, - ) -> QuerySortedManyIter<'w, 's, D, F, I> { - let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); - QuerySortedManyIter { - query_state, - entities: world.entities(), - archetypes: world.archetypes(), - // SAFETY: We only access table data that has been registered in `query_state`. - // This means `world` has permission to access the data we use. - tables: &world.storages().tables, - fetch, - entity_iter: entity_list.into_iter(), - } - } - - /// # Safety - /// The lifetime here is not restrictive enough for Fetch with &mut access, - /// as calling `fetch_next_aliased_unchecked` multiple times can produce multiple - /// references to the same component, leading to unique reference aliasing. - /// - /// It is always safe for shared access. - /// `entity` must stem from `self.entity_iter`, and not have been passed before. - #[inline(always)] - unsafe fn fetch_next_aliased_unchecked(&mut self, entity: Entity) -> D::Item<'w, 's> { - let (location, archetype, table); - // SAFETY: - // `tables` and `archetypes` belong to the same world that the [`QueryIter`] - // was initialized for. - unsafe { - location = self.entities.get(entity).debug_checked_unwrap(); - archetype = self - .archetypes - .get(location.archetype_id) - .debug_checked_unwrap(); - table = self.tables.get(location.table_id).debug_checked_unwrap(); - } - - // SAFETY: `archetype` is from the world that `fetch` was created for, - // `fetch_state` is the state that `fetch` was initialized with - unsafe { - D::set_archetype( - &mut self.fetch, - &self.query_state.fetch_state, - archetype, - table, - ); - } - - // The entity list has already been filtered by the query lens, so we forego filtering here. - // SAFETY: - // - set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype - // - fetch is only called once for each entity. - unsafe { - D::fetch( - &self.query_state.fetch_state, - &mut self.fetch, - entity, - location.table_row, - ) - } - } - - /// Get next result from the query - #[inline(always)] - pub fn fetch_next(&mut self) -> Option> { - let entity = self.entity_iter.next()?; - - // SAFETY: - // We have collected the entity_iter once to drop all internal lens query item - // references. - // We are limiting the returned reference to self, - // making sure this method cannot be called multiple times without getting rid - // of any previously returned unique references first, thus preventing aliasing. - // `entity` is passed from `entity_iter` the first time. - unsafe { D::shrink(self.fetch_next_aliased_unchecked(entity)).into() } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator> - QuerySortedManyIter<'w, 's, D, F, I> -{ - /// Get next result from the query - #[inline(always)] - pub fn fetch_next_back(&mut self) -> Option> { - let entity = self.entity_iter.next_back()?; - - // SAFETY: - // We have collected the entity_iter once to drop all internal lens query item - // references. - // We are limiting the returned reference to self, - // making sure this method cannot be called multiple times without getting rid - // of any previously returned unique references first, thus preventing aliasing. - // `entity` is passed from `entity_iter` the first time. - unsafe { D::shrink(self.fetch_next_aliased_unchecked(entity)).into() } - } -} - -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator> Iterator - for QuerySortedManyIter<'w, 's, D, F, I> -{ - type Item = D::Item<'w, 's>; - - #[inline(always)] - fn next(&mut self) -> Option { - let entity = self.entity_iter.next()?; - // SAFETY: - // It is safe to alias for ReadOnlyWorldQuery. - // `entity` is passed from `entity_iter` the first time. - unsafe { self.fetch_next_aliased_unchecked(entity).into() } - } - - fn size_hint(&self) -> (usize, Option) { - self.entity_iter.size_hint() - } -} - -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: DoubleEndedIterator> - DoubleEndedIterator for QuerySortedManyIter<'w, 's, D, F, I> -{ - #[inline(always)] - fn next_back(&mut self) -> Option { - let entity = self.entity_iter.next_back()?; - // SAFETY: - // It is safe to alias for ReadOnlyWorldQuery. - // `entity` is passed from `entity_iter` the first time. - unsafe { self.fetch_next_aliased_unchecked(entity).into() } - } -} - -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: ExactSizeIterator> - ExactSizeIterator for QuerySortedManyIter<'w, 's, D, F, I> -{ -} - -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug - for QuerySortedManyIter<'w, 's, D, F, I> -{ - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("QuerySortedManyIter").finish() - } -} - -/// An iterator over `K`-sized combinations of query items without repetition. -/// -/// A combination is an arrangement of a collection of items where order does not matter. -/// -/// `K` is the number of items that make up each subset, and the number of items returned by the iterator. -/// `N` is the number of total entities output by the query. -/// -/// For example, given the list [1, 2, 3, 4], where `K` is 2, the combinations without repeats are -/// [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]. -/// And in this case, `N` would be defined as 4 since the size of the input list is 4. -/// -/// The number of combinations depend on how `K` relates to the number of entities matching the [`Query`]: -/// - if `K = N`, only one combination exists, -/// - if `K < N`, there are NCK combinations (see the [performance section] of `Query`), -/// - if `K > N`, there are no combinations. -/// -/// The output combination is not guaranteed to have any order of iteration. -/// -/// # Usage -/// -/// This type is returned by calling [`Query::iter_combinations`] or [`Query::iter_combinations_mut`]. -/// -/// It implements [`Iterator`] only if it iterates over read-only query items ([learn more]). -/// -/// In the case of mutable query items, it can be iterated by calling [`fetch_next`] in a `while let` loop. -/// -/// # Examples -/// -/// The following example shows how to traverse the iterator when the query items are read-only. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// fn some_system(query: Query<&ComponentA>) { -/// for [a1, a2] in query.iter_combinations() { -/// // ... -/// } -/// } -/// ``` -/// -/// The following example shows how `fetch_next` should be called with a `while let` loop to traverse the iterator when the query items are mutable. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// fn some_system(mut query: Query<&mut ComponentA>) { -/// let mut combinations = query.iter_combinations_mut(); -/// while let Some([a1, a2]) = combinations.fetch_next() { -/// // ... -/// } -/// } -/// ``` -/// -/// [`fetch_next`]: Self::fetch_next -/// [learn more]: Self#impl-Iterator -/// [performance section]: crate::system::Query#performance -/// [`Query`]: crate::system::Query -/// [`Query::iter_combinations`]: crate::system::Query::iter_combinations -/// [`Query::iter_combinations_mut`]: crate::system::Query::iter_combinations_mut -pub struct QueryCombinationIter<'w, 's, D: QueryData, F: QueryFilter, const K: usize> { - tables: &'w Tables, - archetypes: &'w Archetypes, - query_state: &'s QueryState, - cursors: [QueryIterationCursor<'w, 's, D, F>; K], -} - -impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter<'w, 's, D, F, K> { - /// # Safety - /// - `world` must have permission to access any of the components registered in `query_state`. - /// - `world` must be the same one used to initialize `query_state`. - pub(crate) unsafe fn new( - world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, - last_run: Tick, - this_run: Tick, - ) -> Self { - assert!(K != 0, "K should not equal to zero"); - // Initialize array with cursors. - // There is no FromIterator on arrays, so instead initialize it manually with MaybeUninit - - let mut array: MaybeUninit<[QueryIterationCursor<'w, 's, D, F>; K]> = MaybeUninit::uninit(); - let ptr = array - .as_mut_ptr() - .cast::>(); - ptr.write(QueryIterationCursor::init( - world, - query_state, - last_run, - this_run, - )); - for slot in (1..K).map(|offset| ptr.add(offset)) { - slot.write(QueryIterationCursor::init_empty( - world, - query_state, - last_run, - this_run, - )); - } - - QueryCombinationIter { - query_state, - // SAFETY: We only access table data that has been registered in `query_state`. - tables: unsafe { &world.storages().tables }, - archetypes: world.archetypes(), - cursors: array.assume_init(), - } - } - - /// # Safety - /// The lifetime here is not restrictive enough for Fetch with &mut access, - /// as calling `fetch_next_aliased_unchecked` multiple times can produce multiple - /// references to the same component, leading to unique reference aliasing. - /// . - /// It is always safe for shared access. - #[inline] - unsafe fn fetch_next_aliased_unchecked(&mut self) -> Option<[D::Item<'w, 's>; K]> { - // PERF: can speed up the following code using `cursor.remaining()` instead of `next_item.is_none()` - // when D::IS_ARCHETYPAL && F::IS_ARCHETYPAL - // - // let `i` be the index of `c`, the last cursor in `self.cursors` that - // returns `K-i` or more elements. - // Make cursor in index `j` for all `j` in `[i, K)` a copy of `c` advanced `j-i+1` times. - // If no such `c` exists, return `None` - 'outer: for i in (0..K).rev() { - match self.cursors[i].next(self.tables, self.archetypes, self.query_state) { - Some(_) => { - for j in (i + 1)..K { - self.cursors[j] = self.cursors[j - 1].clone(); - match self.cursors[j].next(self.tables, self.archetypes, self.query_state) { - Some(_) => {} - None if i > 0 => continue 'outer, - None => return None, - } - } - break; - } - None if i > 0 => continue, - None => return None, - } - } - - let mut values = MaybeUninit::<[D::Item<'w, 's>; K]>::uninit(); - - let ptr = values.as_mut_ptr().cast::>(); - for (offset, cursor) in self.cursors.iter_mut().enumerate() { - ptr.add(offset) - .write(cursor.peek_last(self.query_state).unwrap()); - } - - Some(values.assume_init()) - } - - /// Get next combination of queried components - #[inline] - pub fn fetch_next(&mut self) -> Option<[D::Item<'_, 's>; K]> { - // SAFETY: we are limiting the returned reference to self, - // making sure this method cannot be called multiple times without getting rid - // of any previously returned unique references first, thus preventing aliasing. - unsafe { - self.fetch_next_aliased_unchecked() - .map(|array| array.map(D::shrink)) - } - } -} - -// Iterator type is intentionally implemented only for read-only access. -// Doing so for mutable references would be unsound, because calling `next` -// multiple times would allow multiple owned references to the same data to exist. -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, const K: usize> Iterator - for QueryCombinationIter<'w, 's, D, F, K> -{ - type Item = [D::Item<'w, 's>; K]; - - #[inline] - fn next(&mut self) -> Option { - // Safety: it is safe to alias for ReadOnlyWorldQuery - unsafe { QueryCombinationIter::fetch_next_aliased_unchecked(self) } - } - - fn size_hint(&self) -> (usize, Option) { - // binomial coefficient: (n ; k) = n! / k!(n-k)! = (n*n-1*...*n-k+1) / k! - // See https://en.wikipedia.org/wiki/Binomial_coefficient - // See https://blog.plover.com/math/choose.html for implementation - // It was chosen to reduce overflow potential. - fn choose(n: usize, k: usize) -> Option { - if k > n || n == 0 { - return Some(0); - } - let k = k.min(n - k); - let ks = 1..=k; - let ns = (n - k + 1..=n).rev(); - ks.zip(ns) - .try_fold(1_usize, |acc, (k, n)| Some(acc.checked_mul(n)? / k)) - } - // sum_i=0..k choose(cursors[i].remaining, k-i) - let max_combinations = self - .cursors - .iter() - .enumerate() - .try_fold(0, |acc, (i, cursor)| { - let n = cursor.max_remaining(self.tables, self.archetypes); - Some(acc + choose(n as usize, K - i)?) - }); - - let archetype_query = F::IS_ARCHETYPAL; - let known_max = max_combinations.unwrap_or(usize::MAX); - let min_combinations = if archetype_query { known_max } else { 0 }; - (min_combinations, max_combinations) - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> ExactSizeIterator for QueryIter<'w, 's, D, F> -where - F: ArchetypeFilter, -{ - fn len(&self) -> usize { - self.size_hint().0 - } -} - -// This is correct as [`QueryCombinationIter`] always returns `None` once exhausted. -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, const K: usize> FusedIterator - for QueryCombinationIter<'w, 's, D, F, K> -{ -} - -impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> Debug - for QueryCombinationIter<'w, 's, D, F, K> -{ - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("QueryCombinationIter").finish() - } -} - -struct QueryIterationCursor<'w, 's, D: QueryData, F: QueryFilter> { - // whether the query iteration is dense or not. Mirrors QueryState's `is_dense` field. - is_dense: bool, - storage_id_iter: core::slice::Iter<'s, StorageId>, - table_entities: &'w [Entity], - archetype_entities: &'w [ArchetypeEntity], - fetch: D::Fetch<'w>, - filter: F::Fetch<'w>, - // length of the table or length of the archetype, depending on whether both `D`'s and `F`'s fetches are dense - current_len: u32, - // either table row or archetype index, depending on whether both `D`'s and `F`'s fetches are dense - current_row: u32, -} - -impl Clone for QueryIterationCursor<'_, '_, D, F> { - fn clone(&self) -> Self { - Self { - is_dense: self.is_dense, - storage_id_iter: self.storage_id_iter.clone(), - table_entities: self.table_entities, - archetype_entities: self.archetype_entities, - fetch: self.fetch.clone(), - filter: self.filter.clone(), - current_len: self.current_len, - current_row: self.current_row, - } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { - /// # Safety - /// - `world` must have permission to access any of the components registered in `query_state`. - /// - `world` must be the same one used to initialize `query_state`. - unsafe fn init_empty( - world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, - last_run: Tick, - this_run: Tick, - ) -> Self { - QueryIterationCursor { - storage_id_iter: [].iter(), - ..Self::init(world, query_state, last_run, this_run) - } - } - - /// # Safety - /// - `world` must have permission to access any of the components registered in `query_state`. - /// - `world` must be the same one used to initialize `query_state`. - unsafe fn init( - world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, - last_run: Tick, - this_run: Tick, - ) -> Self { - let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); - let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run); - QueryIterationCursor { - fetch, - filter, - table_entities: &[], - archetype_entities: &[], - storage_id_iter: query_state.matched_storage_ids.iter(), - is_dense: query_state.is_dense, - current_len: 0, - current_row: 0, - } - } - - fn reborrow(&mut self) -> QueryIterationCursor<'_, 's, D, F> { - QueryIterationCursor { - is_dense: self.is_dense, - fetch: D::shrink_fetch(self.fetch.clone()), - filter: F::shrink_fetch(self.filter.clone()), - table_entities: self.table_entities, - archetype_entities: self.archetype_entities, - storage_id_iter: self.storage_id_iter.clone(), - current_len: self.current_len, - current_row: self.current_row, - } - } - - /// Retrieve item returned from most recent `next` call again. - /// - /// # Safety - /// The result of `next` and any previous calls to `peek_last` with this row must have been - /// dropped to prevent aliasing mutable references. - #[inline] - unsafe fn peek_last(&mut self, query_state: &'s QueryState) -> Option> { - if self.current_row > 0 { - let index = self.current_row - 1; - if self.is_dense { - // SAFETY: This must have been called previously in `next` as `current_row > 0` - let entity = unsafe { self.table_entities.get_unchecked(index as usize) }; - // SAFETY: - // - `set_table` must have been called previously either in `next` or before it. - // - `*entity` and `index` are in the current table. - unsafe { - Some(D::fetch( - &query_state.fetch_state, - &mut self.fetch, - *entity, - // SAFETY: This is from an exclusive range, so it can't be max. - TableRow::new(NonMaxU32::new_unchecked(index)), - )) - } - } else { - // SAFETY: This must have been called previously in `next` as `current_row > 0` - let archetype_entity = - unsafe { self.archetype_entities.get_unchecked(index as usize) }; - // SAFETY: - // - `set_archetype` must have been called previously either in `next` or before it. - // - `archetype_entity.id()` and `archetype_entity.table_row()` are in the current archetype. - unsafe { - Some(D::fetch( - &query_state.fetch_state, - &mut self.fetch, - archetype_entity.id(), - archetype_entity.table_row(), - )) - } - } - } else { - None - } - } - - /// How many values will this cursor return at most? - /// - /// Note that if `F::IS_ARCHETYPAL`, the return value - /// will be **the exact count of remaining values**. - fn max_remaining(&self, tables: &'w Tables, archetypes: &'w Archetypes) -> u32 { - let ids = self.storage_id_iter.clone(); - let remaining_matched: u32 = if self.is_dense { - // SAFETY: The if check ensures that storage_id_iter stores TableIds - unsafe { ids.map(|id| tables[id.table_id].entity_count()).sum() } - } else { - // SAFETY: The if check ensures that storage_id_iter stores ArchetypeIds - unsafe { ids.map(|id| archetypes[id.archetype_id].len()).sum() } - }; - remaining_matched + self.current_len - self.current_row - } - - // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: - // QueryIter, QueryIterationCursor, QuerySortedIter, QueryManyIter, QuerySortedManyIter, QueryCombinationIter, - // QueryState::par_fold_init_unchecked_manual, QueryState::par_many_fold_init_unchecked_manual, - // QueryState::par_many_unique_fold_init_unchecked_manual - /// # Safety - /// `tables` and `archetypes` must belong to the same world that the [`QueryIterationCursor`] - /// was initialized for. - /// `query_state` must be the same [`QueryState`] that was passed to `init` or `init_empty`. - #[inline(always)] - unsafe fn next( - &mut self, - tables: &'w Tables, - archetypes: &'w Archetypes, - query_state: &'s QueryState, - ) -> Option> { - if self.is_dense { - loop { - // we are on the beginning of the query, or finished processing a table, so skip to the next - if self.current_row == self.current_len { - let table_id = self.storage_id_iter.next()?.table_id; - let table = tables.get(table_id).debug_checked_unwrap(); - if table.is_empty() { - continue; - } - // SAFETY: `table` is from the world that `fetch/filter` were created for, - // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with - unsafe { - D::set_table(&mut self.fetch, &query_state.fetch_state, table); - F::set_table(&mut self.filter, &query_state.filter_state, table); - } - self.table_entities = table.entities(); - self.current_len = table.entity_count(); - self.current_row = 0; - } - - // SAFETY: set_table was called prior. - // `current_row` is a table row in range of the current table, because if it was not, then the above would have been executed. - let entity = - unsafe { self.table_entities.get_unchecked(self.current_row as usize) }; - // SAFETY: The row is less than the u32 len, so it must not be max. - let row = unsafe { TableRow::new(NonMaxU32::new_unchecked(self.current_row)) }; - if !F::filter_fetch(&query_state.filter_state, &mut self.filter, *entity, row) { - self.current_row += 1; - continue; - } - - // SAFETY: - // - set_table was called prior. - // - `current_row` must be a table row in range of the current table, - // because if it was not, then the above would have been executed. - // - fetch is only called once for each `entity`. - let item = - unsafe { D::fetch(&query_state.fetch_state, &mut self.fetch, *entity, row) }; - - self.current_row += 1; - return Some(item); - } - } else { - loop { - if self.current_row == self.current_len { - let archetype_id = self.storage_id_iter.next()?.archetype_id; - let archetype = archetypes.get(archetype_id).debug_checked_unwrap(); - if archetype.is_empty() { - continue; - } - let table = tables.get(archetype.table_id()).debug_checked_unwrap(); - // SAFETY: `archetype` and `tables` are from the world that `fetch/filter` were created for, - // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with - unsafe { - D::set_archetype( - &mut self.fetch, - &query_state.fetch_state, - archetype, - table, - ); - F::set_archetype( - &mut self.filter, - &query_state.filter_state, - archetype, - table, - ); - } - self.archetype_entities = archetype.entities(); - self.current_len = archetype.len(); - self.current_row = 0; - } - - // SAFETY: set_archetype was called prior. - // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. - let archetype_entity = unsafe { - self.archetype_entities - .get_unchecked(self.current_row as usize) - }; - if !F::filter_fetch( - &query_state.filter_state, - &mut self.filter, - archetype_entity.id(), - archetype_entity.table_row(), - ) { - self.current_row += 1; - continue; - } - - // SAFETY: - // - set_archetype was called prior. - // - `current_row` must be an archetype index row in range of the current archetype, - // because if it was not, then the if above would have been executed. - // - fetch is only called once for each `archetype_entity`. - let item = unsafe { - D::fetch( - &query_state.fetch_state, - &mut self.fetch, - archetype_entity.id(), - archetype_entity.table_row(), - ) - }; - self.current_row += 1; - return Some(item); - } - } - } -} - -// A wrapper struct that gives its data a neutral ordering. -#[derive(Copy, Clone)] -struct NeutralOrd(T); - -impl PartialEq for NeutralOrd { - fn eq(&self, _other: &Self) -> bool { - true - } -} - -impl Eq for NeutralOrd {} - -#[expect( - clippy::non_canonical_partial_ord_impl, - reason = "`PartialOrd` and `Ord` on this struct must only ever return `Ordering::Equal`, so we prefer clarity" -)] -impl PartialOrd for NeutralOrd { - fn partial_cmp(&self, _other: &Self) -> Option { - Some(Ordering::Equal) - } -} - -impl Ord for NeutralOrd { - fn cmp(&self, _other: &Self) -> Ordering { - Ordering::Equal - } -} - -#[cfg(test)] -#[expect(clippy::print_stdout, reason = "Allowed in tests.")] -mod tests { - use alloc::vec::Vec; - use std::println; - - use crate::component::Component; - use crate::entity::Entity; - use crate::prelude::World; - - #[derive(Component, Debug, PartialEq, PartialOrd, Clone, Copy)] - struct A(f32); - #[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] - #[component(storage = "SparseSet")] - struct Sparse(usize); - - #[test] - fn query_iter_sorts() { - let mut world = World::new(); - for i in 0..100 { - world.spawn(A(i as f32)); - world.spawn((A(i as f32), Sparse(i))); - world.spawn(Sparse(i)); - } - - let mut query = world.query::(); - - let sort = query.iter(&world).sort::().collect::>(); - assert_eq!(sort.len(), 300); - - let sort_unstable = query - .iter(&world) - .sort_unstable::() - .collect::>(); - - let sort_by = query - .iter(&world) - .sort_by::(Ord::cmp) - .collect::>(); - - let sort_unstable_by = query - .iter(&world) - .sort_unstable_by::(Ord::cmp) - .collect::>(); - - let sort_by_key = query - .iter(&world) - .sort_by_key::(|&e| e) - .collect::>(); - - let sort_unstable_by_key = query - .iter(&world) - .sort_unstable_by_key::(|&e| e) - .collect::>(); - - let sort_by_cached_key = query - .iter(&world) - .sort_by_cached_key::(|&e| e) - .collect::>(); - - let mut sort_v2 = query.iter(&world).collect::>(); - sort_v2.sort(); - - let mut sort_unstable_v2 = query.iter(&world).collect::>(); - sort_unstable_v2.sort_unstable(); - - let mut sort_by_v2 = query.iter(&world).collect::>(); - sort_by_v2.sort_by(Ord::cmp); - - let mut sort_unstable_by_v2 = query.iter(&world).collect::>(); - sort_unstable_by_v2.sort_unstable_by(Ord::cmp); - - let mut sort_by_key_v2 = query.iter(&world).collect::>(); - sort_by_key_v2.sort_by_key(|&e| e); - - let mut sort_unstable_by_key_v2 = query.iter(&world).collect::>(); - sort_unstable_by_key_v2.sort_unstable_by_key(|&e| e); - - let mut sort_by_cached_key_v2 = query.iter(&world).collect::>(); - sort_by_cached_key_v2.sort_by_cached_key(|&e| e); - - assert_eq!(sort, sort_v2); - assert_eq!(sort_unstable, sort_unstable_v2); - assert_eq!(sort_by, sort_by_v2); - assert_eq!(sort_unstable_by, sort_unstable_by_v2); - assert_eq!(sort_by_key, sort_by_key_v2); - assert_eq!(sort_unstable_by_key, sort_unstable_by_key_v2); - assert_eq!(sort_by_cached_key, sort_by_cached_key_v2); - } - - #[test] - #[should_panic] - fn query_iter_sort_after_next() { - let mut world = World::new(); - world.spawn((A(0.),)); - world.spawn((A(1.1),)); - world.spawn((A(2.22),)); - - { - let mut query = world.query::<&A>(); - let mut iter = query.iter(&world); - println!( - "archetype_entities: {} table_entities: {} current_len: {} current_row: {}", - iter.cursor.archetype_entities.len(), - iter.cursor.table_entities.len(), - iter.cursor.current_len, - iter.cursor.current_row - ); - _ = iter.next(); - println!( - "archetype_entities: {} table_entities: {} current_len: {} current_row: {}", - iter.cursor.archetype_entities.len(), - iter.cursor.table_entities.len(), - iter.cursor.current_len, - iter.cursor.current_row - ); - println!("{}", iter.sort::().len()); - } - } - - #[test] - #[should_panic] - fn query_iter_sort_after_next_dense() { - let mut world = World::new(); - world.spawn((Sparse(11),)); - world.spawn((Sparse(22),)); - world.spawn((Sparse(33),)); - - { - let mut query = world.query::<&Sparse>(); - let mut iter = query.iter(&world); - println!( - "before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", - iter.cursor.archetype_entities.len(), - iter.cursor.table_entities.len(), - iter.cursor.current_len, - iter.cursor.current_row - ); - _ = iter.next(); - println!( - "after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", - iter.cursor.archetype_entities.len(), - iter.cursor.table_entities.len(), - iter.cursor.current_len, - iter.cursor.current_row - ); - println!("{}", iter.sort::().len()); - } - } - - #[test] - fn empty_query_iter_sort_after_next_does_not_panic() { - let mut world = World::new(); - { - let mut query = world.query::<(&A, &Sparse)>(); - let mut iter = query.iter(&world); - println!( - "before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", - iter.cursor.archetype_entities.len(), - iter.cursor.table_entities.len(), - iter.cursor.current_len, - iter.cursor.current_row - ); - _ = iter.next(); - println!( - "after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", - iter.cursor.archetype_entities.len(), - iter.cursor.table_entities.len(), - iter.cursor.current_len, - iter.cursor.current_row - ); - println!("{}", iter.sort::().len()); - } - } - - #[test] - fn query_iter_cursor_state_non_empty_after_next() { - let mut world = World::new(); - world.spawn((A(0.), Sparse(11))); - world.spawn((A(1.1), Sparse(22))); - world.spawn((A(2.22), Sparse(33))); - { - let mut query = world.query::<(&A, &Sparse)>(); - let mut iter = query.iter(&world); - println!( - "before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", - iter.cursor.archetype_entities.len(), - iter.cursor.table_entities.len(), - iter.cursor.current_len, - iter.cursor.current_row - ); - assert!(iter.cursor.table_entities.len() | iter.cursor.archetype_entities.len() == 0); - _ = iter.next(); - println!( - "after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", - iter.cursor.archetype_entities.len(), - iter.cursor.table_entities.len(), - iter.cursor.current_len, - iter.cursor.current_row - ); - assert!( - ( - iter.cursor.table_entities.len(), - iter.cursor.archetype_entities.len() - ) != (0, 0) - ); - } - } - - #[test] - fn query_iter_many_sorts() { - let mut world = World::new(); - - let entity_list: &Vec<_> = &world - .spawn_batch([A(0.), A(1.), A(2.), A(3.), A(4.)]) - .collect(); - - let mut query = world.query::(); - - let sort = query - .iter_many(&world, entity_list) - .sort::() - .collect::>(); - - let sort_unstable = query - .iter_many(&world, entity_list) - .sort_unstable::() - .collect::>(); - - let sort_by = query - .iter_many(&world, entity_list) - .sort_by::(Ord::cmp) - .collect::>(); - - let sort_unstable_by = query - .iter_many(&world, entity_list) - .sort_unstable_by::(Ord::cmp) - .collect::>(); - - let sort_by_key = query - .iter_many(&world, entity_list) - .sort_by_key::(|&e| e) - .collect::>(); - - let sort_unstable_by_key = query - .iter_many(&world, entity_list) - .sort_unstable_by_key::(|&e| e) - .collect::>(); - - let sort_by_cached_key = query - .iter_many(&world, entity_list) - .sort_by_cached_key::(|&e| e) - .collect::>(); - - let mut sort_v2 = query.iter_many(&world, entity_list).collect::>(); - sort_v2.sort(); - - let mut sort_unstable_v2 = query.iter_many(&world, entity_list).collect::>(); - sort_unstable_v2.sort_unstable(); - - let mut sort_by_v2 = query.iter_many(&world, entity_list).collect::>(); - sort_by_v2.sort_by(Ord::cmp); - - let mut sort_unstable_by_v2 = query.iter_many(&world, entity_list).collect::>(); - sort_unstable_by_v2.sort_unstable_by(Ord::cmp); - - let mut sort_by_key_v2 = query.iter_many(&world, entity_list).collect::>(); - sort_by_key_v2.sort_by_key(|&e| e); - - let mut sort_unstable_by_key_v2 = query.iter_many(&world, entity_list).collect::>(); - sort_unstable_by_key_v2.sort_unstable_by_key(|&e| e); - - let mut sort_by_cached_key_v2 = query.iter_many(&world, entity_list).collect::>(); - sort_by_cached_key_v2.sort_by_cached_key(|&e| e); - - assert_eq!(sort, sort_v2); - assert_eq!(sort_unstable, sort_unstable_v2); - assert_eq!(sort_by, sort_by_v2); - assert_eq!(sort_unstable_by, sort_unstable_by_v2); - assert_eq!(sort_by_key, sort_by_key_v2); - assert_eq!(sort_unstable_by_key, sort_unstable_by_key_v2); - assert_eq!(sort_by_cached_key, sort_by_cached_key_v2); - } - - #[test] - fn query_iter_many_sort_doesnt_panic_after_next() { - let mut world = World::new(); - - let entity_list: &Vec<_> = &world - .spawn_batch([A(0.), A(1.), A(2.), A(3.), A(4.)]) - .collect(); - - let mut query = world.query::(); - let mut iter = query.iter_many(&world, entity_list); - - _ = iter.next(); - - iter.sort::(); - - let mut query_2 = world.query::<&mut A>(); - let mut iter_2 = query_2.iter_many_mut(&mut world, entity_list); - - _ = iter_2.fetch_next(); - - iter_2.sort::(); - } - - // This test should be run with miri to check for UB caused by aliasing. - // The lens items created during the sort must not be live at the same time as the mutable references returned from the iterator. - #[test] - fn query_iter_many_sorts_duplicate_entities_no_ub() { - #[derive(Component, Ord, PartialOrd, Eq, PartialEq)] - struct C(usize); - - let mut world = World::new(); - let id = world.spawn(C(10)).id(); - let mut query_state = world.query::<&mut C>(); - - { - let mut query = query_state.iter_many_mut(&mut world, [id, id]).sort::<&C>(); - while query.fetch_next().is_some() {} - } - { - let mut query = query_state - .iter_many_mut(&mut world, [id, id]) - .sort_unstable::<&C>(); - while query.fetch_next().is_some() {} - } - { - let mut query = query_state - .iter_many_mut(&mut world, [id, id]) - .sort_by::<&C>(|l, r| Ord::cmp(l, r)); - while query.fetch_next().is_some() {} - } - { - let mut query = query_state - .iter_many_mut(&mut world, [id, id]) - .sort_unstable_by::<&C>(|l, r| Ord::cmp(l, r)); - while query.fetch_next().is_some() {} - } - { - let mut query = query_state - .iter_many_mut(&mut world, [id, id]) - .sort_by_key::<&C, _>(|d| d.0); - while query.fetch_next().is_some() {} - } - { - let mut query = query_state - .iter_many_mut(&mut world, [id, id]) - .sort_unstable_by_key::<&C, _>(|d| d.0); - while query.fetch_next().is_some() {} - } - { - let mut query = query_state - .iter_many_mut(&mut world, [id, id]) - .sort_by_cached_key::<&C, _>(|d| d.0); - while query.fetch_next().is_some() {} - } - } -} diff --git a/src/query/par_iter.rs b/src/query/par_iter.rs deleted file mode 100644 index b8d8618..0000000 --- a/src/query/par_iter.rs +++ /dev/null @@ -1,464 +0,0 @@ -use crate::{ - batching::BatchingStrategy, - component::Tick, - entity::{EntityEquivalent, UniqueEntityEquivalentVec}, - world::unsafe_world_cell::UnsafeWorldCell, -}; - -use super::{QueryData, QueryFilter, QueryItem, QueryState, ReadOnlyQueryData}; - -use alloc::vec::Vec; - -/// A parallel iterator over query results of a [`Query`](crate::system::Query). -/// -/// This struct is created by the [`Query::par_iter`](crate::system::Query::par_iter) and -/// [`Query::par_iter_mut`](crate::system::Query::par_iter_mut) methods. -pub struct QueryParIter<'w, 's, D: QueryData, F: QueryFilter> { - pub(crate) world: UnsafeWorldCell<'w>, - pub(crate) state: &'s QueryState, - pub(crate) last_run: Tick, - pub(crate) this_run: Tick, - pub(crate) batching_strategy: BatchingStrategy, -} - -impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { - /// Changes the batching strategy used when iterating. - /// - /// For more information on how this affects the resultant iteration, see - /// [`BatchingStrategy`]. - pub fn batching_strategy(mut self, strategy: BatchingStrategy) -> Self { - self.batching_strategy = strategy; - self - } - - /// Runs `func` on each query result in parallel. - /// - /// # Panics - /// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[inline] - pub fn for_each) + Send + Sync + Clone>(self, func: FN) { - self.for_each_init(|| {}, |_, item| func(item)); - } - - /// Runs `func` on each query result in parallel on a value returned by `init`. - /// - /// `init` may be called multiple times per thread, and the values returned may be discarded between tasks on any given thread. - /// Callers should avoid using this function as if it were a parallel version - /// of [`Iterator::fold`]. - /// - /// # Example - /// - /// ``` - /// use bevy_utils::Parallel; - /// use crate::{bevy_ecs::prelude::Component, bevy_ecs::system::Query}; - /// #[derive(Component)] - /// struct T; - /// fn system(query: Query<&T>){ - /// let mut queue: Parallel = Parallel::default(); - /// // queue.borrow_local_mut() will get or create a thread_local queue for each task/thread; - /// query.par_iter().for_each_init(|| queue.borrow_local_mut(),|local_queue, item| { - /// **local_queue += 1; - /// }); - /// - /// // collect value from every thread - /// let entity_count: usize = queue.iter_mut().map(|v| *v).sum(); - /// } - /// ``` - /// - /// # Panics - /// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[inline] - pub fn for_each_init(self, init: INIT, func: FN) - where - FN: Fn(&mut T, QueryItem<'w, 's, D>) + Send + Sync + Clone, - INIT: Fn() -> T + Sync + Send + Clone, - { - let func = |mut init, item| { - func(&mut init, item); - init - }; - #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))] - { - let init = init(); - // SAFETY: - // This method can only be called once per instance of QueryParIter, - // which ensures that mutable queries cannot be executed multiple times at once. - // Mutable instances of QueryParIter can only be created via an exclusive borrow of a - // Query or a World, which ensures that multiple aliasing QueryParIters cannot exist - // at the same time. - unsafe { - self.state - .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) - .into_iter() - .fold(init, func); - } - } - #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - { - let thread_count = bevy_tasks::ComputeTaskPool::get().thread_num(); - if thread_count <= 1 { - let init = init(); - // SAFETY: See the safety comment above. - unsafe { - self.state - .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) - .into_iter() - .fold(init, func); - } - } else { - // Need a batch size of at least 1. - let batch_size = self.get_batch_size(thread_count).max(1); - // SAFETY: See the safety comment above. - unsafe { - self.state.par_fold_init_unchecked_manual( - init, - self.world, - batch_size, - func, - self.last_run, - self.this_run, - ); - } - } - } - } - - #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - fn get_batch_size(&self, thread_count: usize) -> u32 { - let max_items = || { - let id_iter = self.state.matched_storage_ids.iter(); - if self.state.is_dense { - // SAFETY: We only access table metadata. - let tables = unsafe { &self.world.world_metadata().storages().tables }; - id_iter - // SAFETY: The if check ensures that matched_storage_ids stores TableIds - .map(|id| unsafe { tables[id.table_id].entity_count() }) - .max() - } else { - let archetypes = &self.world.archetypes(); - id_iter - // SAFETY: The if check ensures that matched_storage_ids stores ArchetypeIds - .map(|id| unsafe { archetypes[id.archetype_id].len() }) - .max() - } - .map(|v| v as usize) - .unwrap_or(0) - }; - self.batching_strategy - .calc_batch_size(max_items, thread_count) as u32 - } -} - -/// A parallel iterator over the unique query items generated from an [`Entity`] list. -/// -/// This struct is created by the [`Query::par_iter_many`] method. -/// -/// [`Entity`]: crate::entity::Entity -/// [`Query::par_iter_many`]: crate::system::Query::par_iter_many -pub struct QueryParManyIter<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent> { - pub(crate) world: UnsafeWorldCell<'w>, - pub(crate) state: &'s QueryState, - pub(crate) entity_list: Vec, - pub(crate) last_run: Tick, - pub(crate) this_run: Tick, - pub(crate) batching_strategy: BatchingStrategy, -} - -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityEquivalent + Sync> - QueryParManyIter<'w, 's, D, F, E> -{ - /// Changes the batching strategy used when iterating. - /// - /// For more information on how this affects the resultant iteration, see - /// [`BatchingStrategy`]. - pub fn batching_strategy(mut self, strategy: BatchingStrategy) -> Self { - self.batching_strategy = strategy; - self - } - - /// Runs `func` on each query result in parallel. - /// - /// # Panics - /// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[inline] - pub fn for_each) + Send + Sync + Clone>(self, func: FN) { - self.for_each_init(|| {}, |_, item| func(item)); - } - - /// Runs `func` on each query result in parallel on a value returned by `init`. - /// - /// `init` may be called multiple times per thread, and the values returned may be discarded between tasks on any given thread. - /// Callers should avoid using this function as if it were a parallel version - /// of [`Iterator::fold`]. - /// - /// # Example - /// - /// ``` - /// use bevy_utils::Parallel; - /// use crate::{bevy_ecs::prelude::{Component, Res, Resource, Entity}, bevy_ecs::system::Query}; - /// # use core::slice; - /// use bevy_platform::prelude::Vec; - /// # fn some_expensive_operation(_item: &T) -> usize { - /// # 0 - /// # } - /// - /// #[derive(Component)] - /// struct T; - /// - /// #[derive(Resource)] - /// struct V(Vec); - /// - /// impl<'a> IntoIterator for &'a V { - /// // ... - /// # type Item = &'a Entity; - /// # type IntoIter = slice::Iter<'a, Entity>; - /// # - /// # fn into_iter(self) -> Self::IntoIter { - /// # self.0.iter() - /// # } - /// } - /// - /// fn system(query: Query<&T>, entities: Res){ - /// let mut queue: Parallel = Parallel::default(); - /// // queue.borrow_local_mut() will get or create a thread_local queue for each task/thread; - /// query.par_iter_many(&entities).for_each_init(|| queue.borrow_local_mut(),|local_queue, item| { - /// **local_queue += some_expensive_operation(item); - /// }); - /// - /// // collect value from every thread - /// let final_value: usize = queue.iter_mut().map(|v| *v).sum(); - /// } - /// ``` - /// - /// # Panics - /// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[inline] - pub fn for_each_init(self, init: INIT, func: FN) - where - FN: Fn(&mut T, QueryItem<'w, 's, D>) + Send + Sync + Clone, - INIT: Fn() -> T + Sync + Send + Clone, - { - let func = |mut init, item| { - func(&mut init, item); - init - }; - #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))] - { - let init = init(); - // SAFETY: - // This method can only be called once per instance of QueryParManyIter, - // which ensures that mutable queries cannot be executed multiple times at once. - // Mutable instances of QueryParManyUniqueIter can only be created via an exclusive borrow of a - // Query or a World, which ensures that multiple aliasing QueryParManyIters cannot exist - // at the same time. - unsafe { - self.state - .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) - .iter_many_inner(&self.entity_list) - .fold(init, func); - } - } - #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - { - let thread_count = bevy_tasks::ComputeTaskPool::get().thread_num(); - if thread_count <= 1 { - let init = init(); - // SAFETY: See the safety comment above. - unsafe { - self.state - .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) - .iter_many_inner(&self.entity_list) - .fold(init, func); - } - } else { - // Need a batch size of at least 1. - let batch_size = self.get_batch_size(thread_count).max(1); - // SAFETY: See the safety comment above. - unsafe { - self.state.par_many_fold_init_unchecked_manual( - init, - self.world, - &self.entity_list, - batch_size, - func, - self.last_run, - self.this_run, - ); - } - } - } - } - - #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - fn get_batch_size(&self, thread_count: usize) -> u32 { - self.batching_strategy - .calc_batch_size(|| self.entity_list.len(), thread_count) as u32 - } -} - -/// A parallel iterator over the unique query items generated from an [`EntitySet`]. -/// -/// This struct is created by the [`Query::par_iter_many_unique`] and [`Query::par_iter_many_unique_mut`] methods. -/// -/// [`EntitySet`]: crate::entity::EntitySet -/// [`Query::par_iter_many_unique`]: crate::system::Query::par_iter_many_unique -/// [`Query::par_iter_many_unique_mut`]: crate::system::Query::par_iter_many_unique_mut -pub struct QueryParManyUniqueIter<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent + Sync> -{ - pub(crate) world: UnsafeWorldCell<'w>, - pub(crate) state: &'s QueryState, - pub(crate) entity_list: UniqueEntityEquivalentVec, - pub(crate) last_run: Tick, - pub(crate) this_run: Tick, - pub(crate) batching_strategy: BatchingStrategy, -} - -impl<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent + Sync> - QueryParManyUniqueIter<'w, 's, D, F, E> -{ - /// Changes the batching strategy used when iterating. - /// - /// For more information on how this affects the resultant iteration, see - /// [`BatchingStrategy`]. - pub fn batching_strategy(mut self, strategy: BatchingStrategy) -> Self { - self.batching_strategy = strategy; - self - } - - /// Runs `func` on each query result in parallel. - /// - /// # Panics - /// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[inline] - pub fn for_each) + Send + Sync + Clone>(self, func: FN) { - self.for_each_init(|| {}, |_, item| func(item)); - } - - /// Runs `func` on each query result in parallel on a value returned by `init`. - /// - /// `init` may be called multiple times per thread, and the values returned may be discarded between tasks on any given thread. - /// Callers should avoid using this function as if it were a parallel version - /// of [`Iterator::fold`]. - /// - /// # Example - /// - /// ``` - /// use bevy_utils::Parallel; - /// use crate::{bevy_ecs::{prelude::{Component, Res, Resource, Entity}, entity::UniqueEntityVec, system::Query}}; - /// # use core::slice; - /// # use crate::bevy_ecs::entity::UniqueEntityIter; - /// # fn some_expensive_operation(_item: &T) -> usize { - /// # 0 - /// # } - /// - /// #[derive(Component)] - /// struct T; - /// - /// #[derive(Resource)] - /// struct V(UniqueEntityVec); - /// - /// impl<'a> IntoIterator for &'a V { - /// // ... - /// # type Item = &'a Entity; - /// # type IntoIter = UniqueEntityIter>; - /// # - /// # fn into_iter(self) -> Self::IntoIter { - /// # self.0.iter() - /// # } - /// } - /// - /// fn system(query: Query<&T>, entities: Res){ - /// let mut queue: Parallel = Parallel::default(); - /// // queue.borrow_local_mut() will get or create a thread_local queue for each task/thread; - /// query.par_iter_many_unique(&entities).for_each_init(|| queue.borrow_local_mut(),|local_queue, item| { - /// **local_queue += some_expensive_operation(item); - /// }); - /// - /// // collect value from every thread - /// let final_value: usize = queue.iter_mut().map(|v| *v).sum(); - /// } - /// ``` - /// - /// # Panics - /// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[inline] - pub fn for_each_init(self, init: INIT, func: FN) - where - FN: Fn(&mut T, QueryItem<'w, 's, D>) + Send + Sync + Clone, - INIT: Fn() -> T + Sync + Send + Clone, - { - let func = |mut init, item| { - func(&mut init, item); - init - }; - #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))] - { - let init = init(); - // SAFETY: - // This method can only be called once per instance of QueryParManyUniqueIter, - // which ensures that mutable queries cannot be executed multiple times at once. - // Mutable instances of QueryParManyUniqueIter can only be created via an exclusive borrow of a - // Query or a World, which ensures that multiple aliasing QueryParManyUniqueIters cannot exist - // at the same time. - unsafe { - self.state - .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) - .iter_many_unique_inner(self.entity_list) - .fold(init, func); - } - } - #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - { - let thread_count = bevy_tasks::ComputeTaskPool::get().thread_num(); - if thread_count <= 1 { - let init = init(); - // SAFETY: See the safety comment above. - unsafe { - self.state - .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) - .iter_many_unique_inner(self.entity_list) - .fold(init, func); - } - } else { - // Need a batch size of at least 1. - let batch_size = self.get_batch_size(thread_count).max(1); - // SAFETY: See the safety comment above. - unsafe { - self.state.par_many_unique_fold_init_unchecked_manual( - init, - self.world, - &self.entity_list, - batch_size, - func, - self.last_run, - self.this_run, - ); - } - } - } - } - - #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - fn get_batch_size(&self, thread_count: usize) -> u32 { - self.batching_strategy - .calc_batch_size(|| self.entity_list.len(), thread_count) as u32 - } -} diff --git a/src/query/state.rs b/src/query/state.rs deleted file mode 100644 index 00d8b6f..0000000 --- a/src/query/state.rs +++ /dev/null @@ -1,2243 +0,0 @@ -use crate::{ - archetype::{Archetype, ArchetypeGeneration, ArchetypeId}, - component::{ComponentId, Tick}, - entity::{Entity, EntityEquivalent, EntitySet, UniqueEntityArray}, - entity_disabling::DefaultQueryFilters, - prelude::FromWorld, - query::{FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, WorldQuery}, - storage::{SparseSetIndex, TableId}, - system::Query, - world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, -}; - -#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] -use crate::entity::UniqueEntityEquivalentSlice; - -use alloc::vec::Vec; -use bevy_utils::prelude::DebugName; -use core::{fmt, ptr}; -use fixedbitset::FixedBitSet; -use log::warn; -#[cfg(feature = "trace")] -use tracing::Span; - -use super::{ - NopWorldQuery, QueryBuilder, QueryData, QueryEntityError, QueryFilter, QueryManyIter, - QueryManyUniqueIter, QuerySingleError, ROQueryItem, ReadOnlyQueryData, -}; - -/// An ID for either a table or an archetype. Used for Query iteration. -/// -/// Query iteration is exclusively dense (over tables) or archetypal (over archetypes) based on whether -/// the query filters are dense or not. This is represented by the [`QueryState::is_dense`] field. -/// -/// Note that `D::IS_DENSE` and `F::IS_DENSE` have no relationship with `QueryState::is_dense` and -/// any combination of their values can happen. -/// -/// This is a union instead of an enum as the usage is determined at compile time, as all [`StorageId`]s for -/// a [`QueryState`] will be all [`TableId`]s or all [`ArchetypeId`]s, and not a mixture of both. This -/// removes the need for discriminator to minimize memory usage and branching during iteration, but requires -/// a safety invariant be verified when disambiguating them. -/// -/// # Safety -/// Must be initialized and accessed as a [`TableId`], if both generic parameters to the query are dense. -/// Must be initialized and accessed as an [`ArchetypeId`] otherwise. -#[derive(Clone, Copy)] -pub(super) union StorageId { - pub(super) table_id: TableId, - pub(super) archetype_id: ArchetypeId, -} - -/// Provides scoped access to a [`World`] state according to a given [`QueryData`] and [`QueryFilter`]. -/// -/// This data is cached between system runs, and is used to: -/// - store metadata about which [`Table`] or [`Archetype`] are matched by the query. "Matched" means -/// that the query will iterate over the data in the matched table/archetype. -/// - cache the [`State`] needed to compute the [`Fetch`] struct used to retrieve data -/// from a specific [`Table`] or [`Archetype`] -/// - build iterators that can iterate over the query results -/// -/// [`State`]: crate::query::world_query::WorldQuery::State -/// [`Fetch`]: crate::query::world_query::WorldQuery::Fetch -/// [`Table`]: crate::storage::Table -#[repr(C)] -// SAFETY NOTE: -// Do not add any new fields that use the `D` or `F` generic parameters as this may -// make `QueryState::as_transmuted_state` unsound if not done with care. -pub struct QueryState { - world_id: WorldId, - pub(crate) archetype_generation: ArchetypeGeneration, - /// Metadata about the [`Table`](crate::storage::Table)s matched by this query. - pub(crate) matched_tables: FixedBitSet, - /// Metadata about the [`Archetype`]s matched by this query. - pub(crate) matched_archetypes: FixedBitSet, - /// [`FilteredAccess`] computed by combining the `D` and `F` access. Used to check which other queries - /// this query can run in parallel with. - /// Note that because we do a zero-cost reference conversion in `Query::as_readonly`, - /// the access for a read-only query may include accesses for the original mutable version, - /// but the `Query` does not have exclusive access to those components. - pub(crate) component_access: FilteredAccess, - // NOTE: we maintain both a bitset and a vec because iterating the vec is faster - pub(super) matched_storage_ids: Vec, - // Represents whether this query iteration is dense or not. When this is true - // `matched_storage_ids` stores `TableId`s, otherwise it stores `ArchetypeId`s. - pub(super) is_dense: bool, - pub(crate) fetch_state: D::State, - pub(crate) filter_state: F::State, - #[cfg(feature = "trace")] - par_iter_span: Span, -} - -impl fmt::Debug for QueryState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("QueryState") - .field("world_id", &self.world_id) - .field("matched_table_count", &self.matched_tables.count_ones(..)) - .field( - "matched_archetype_count", - &self.matched_archetypes.count_ones(..), - ) - .finish_non_exhaustive() - } -} - -impl FromWorld for QueryState { - fn from_world(world: &mut World) -> Self { - world.query_filtered() - } -} - -impl QueryState { - /// Converts this `QueryState` reference to a `QueryState` that does not access anything mutably. - pub fn as_readonly(&self) -> &QueryState { - // SAFETY: invariant on `WorldQuery` trait upholds that `D::ReadOnly` and `F::ReadOnly` - // have a subset of the access, and match the exact same archetypes/tables as `D`/`F` respectively. - unsafe { self.as_transmuted_state::() } - } - - /// Converts this `QueryState` reference to a `QueryState` that does not return any data - /// which can be faster. - /// - /// This doesn't use `NopWorldQuery` as it loses filter functionality, for example - /// `NopWorldQuery>` is functionally equivalent to `With`. - pub(crate) fn as_nop(&self) -> &QueryState, F> { - // SAFETY: `NopWorldQuery` doesn't have any accesses and defers to - // `D` for table/archetype matching - unsafe { self.as_transmuted_state::, F>() } - } - - /// Converts this `QueryState` reference to any other `QueryState` with - /// the same `WorldQuery::State` associated types. - /// - /// Consider using `as_readonly` or `as_nop` instead which are safe functions. - /// - /// # Safety - /// - /// `NewD` must have a subset of the access that `D` does and match the exact same archetypes/tables - /// `NewF` must have a subset of the access that `F` does and match the exact same archetypes/tables - pub(crate) unsafe fn as_transmuted_state< - NewD: ReadOnlyQueryData, - NewF: QueryFilter, - >( - &self, - ) -> &QueryState { - &*ptr::from_ref(self).cast::>() - } - - /// Returns the components accessed by this query. - pub fn component_access(&self) -> &FilteredAccess { - &self.component_access - } - - /// Returns the tables matched by this query. - pub fn matched_tables(&self) -> impl Iterator + '_ { - self.matched_tables.ones().map(TableId::from_usize) - } - - /// Returns the archetypes matched by this query. - pub fn matched_archetypes(&self) -> impl Iterator + '_ { - self.matched_archetypes.ones().map(ArchetypeId::new) - } - - /// Creates a new [`QueryState`] from a given [`World`] and inherits the result of `world.id()`. - pub fn new(world: &mut World) -> Self { - let mut state = Self::new_uninitialized(world); - state.update_archetypes(world); - state - } - - /// Creates a new [`QueryState`] from an immutable [`World`] reference and inherits the result of `world.id()`. - /// - /// This function may fail if, for example, - /// the components that make up this query have not been registered into the world. - pub fn try_new(world: &World) -> Option { - let mut state = Self::try_new_uninitialized(world)?; - state.update_archetypes(world); - Some(state) - } - - /// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet - /// - /// `new_archetype` and its variants must be called on all of the World's archetypes before the - /// state can return valid query results. - fn new_uninitialized(world: &mut World) -> Self { - let fetch_state = D::init_state(world); - let filter_state = F::init_state(world); - Self::from_states_uninitialized(world, fetch_state, filter_state) - } - - /// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet - /// - /// `new_archetype` and its variants must be called on all of the World's archetypes before the - /// state can return valid query results. - fn try_new_uninitialized(world: &World) -> Option { - let fetch_state = D::get_state(world.components())?; - let filter_state = F::get_state(world.components())?; - Some(Self::from_states_uninitialized( - world, - fetch_state, - filter_state, - )) - } - - /// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet - /// - /// `new_archetype` and its variants must be called on all of the World's archetypes before the - /// state can return valid query results. - fn from_states_uninitialized( - world: &World, - fetch_state: ::State, - filter_state: ::State, - ) -> Self { - let mut component_access = FilteredAccess::default(); - D::update_component_access(&fetch_state, &mut component_access); - - // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the - // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch - // because they are evaluated *before* a specific reference is constructed. - let mut filter_component_access = FilteredAccess::default(); - F::update_component_access(&filter_state, &mut filter_component_access); - - // Merge the temporary filter access with the main access. This ensures that filter access is - // properly considered in a global "cross-query" context (both within systems and across systems). - component_access.extend(&filter_component_access); - - // For queries without dynamic filters the dense-ness of the query is equal to the dense-ness - // of its static type parameters. - let mut is_dense = D::IS_DENSE && F::IS_DENSE; - - if let Some(default_filters) = world.get_resource::() { - default_filters.modify_access(&mut component_access); - is_dense &= default_filters.is_dense(world.components()); - } - - Self { - world_id: world.id(), - archetype_generation: ArchetypeGeneration::initial(), - matched_storage_ids: Vec::new(), - is_dense, - fetch_state, - filter_state, - component_access, - matched_tables: Default::default(), - matched_archetypes: Default::default(), - #[cfg(feature = "trace")] - par_iter_span: tracing::info_span!( - "par_for_each", - query = core::any::type_name::(), - filter = core::any::type_name::(), - ), - } - } - - /// Creates a new [`QueryState`] from a given [`QueryBuilder`] and inherits its [`FilteredAccess`]. - pub fn from_builder(builder: &mut QueryBuilder) -> Self { - let mut fetch_state = D::init_state(builder.world_mut()); - let filter_state = F::init_state(builder.world_mut()); - - let mut component_access = FilteredAccess::default(); - D::update_component_access(&fetch_state, &mut component_access); - D::provide_extra_access( - &mut fetch_state, - component_access.access_mut(), - builder.access().access(), - ); - - let mut component_access = builder.access().clone(); - - // For dynamic queries the dense-ness is given by the query builder. - let mut is_dense = builder.is_dense(); - - if let Some(default_filters) = builder.world().get_resource::() { - default_filters.modify_access(&mut component_access); - is_dense &= default_filters.is_dense(builder.world().components()); - } - - let mut state = Self { - world_id: builder.world().id(), - archetype_generation: ArchetypeGeneration::initial(), - matched_storage_ids: Vec::new(), - is_dense, - fetch_state, - filter_state, - component_access, - matched_tables: Default::default(), - matched_archetypes: Default::default(), - #[cfg(feature = "trace")] - par_iter_span: tracing::info_span!( - "par_for_each", - data = core::any::type_name::(), - filter = core::any::type_name::(), - ), - }; - state.update_archetypes(builder.world()); - state - } - - /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. - /// - /// This will create read-only queries, see [`Self::query_mut`] for mutable queries. - pub fn query<'w, 's>(&'s mut self, world: &'w World) -> Query<'w, 's, D::ReadOnly, F> { - self.update_archetypes(world); - self.query_manual(world) - } - - /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. - /// - /// This method is slightly more efficient than [`QueryState::query`] in some situations, since - /// it does not update this instance's internal cache. The resulting query may skip an entity that - /// belongs to an archetype that has not been cached. - /// - /// To ensure that the cache is up to date, call [`QueryState::update_archetypes`] before this method. - /// The cache is also updated in [`QueryState::new`], [`QueryState::get`], or any method with mutable - /// access to `self`. - /// - /// This will create read-only queries, see [`Self::query_mut`] for mutable queries. - pub fn query_manual<'w, 's>(&'s self, world: &'w World) -> Query<'w, 's, D::ReadOnly, F> { - self.validate_world(world.id()); - // SAFETY: - // - We have read access to the entire world, and we call `as_readonly()` so the query only performs read access. - // - We called `validate_world`. - unsafe { - self.as_readonly() - .query_unchecked_manual(world.as_unsafe_world_cell_readonly()) - } - } - - /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. - pub fn query_mut<'w, 's>(&'s mut self, world: &'w mut World) -> Query<'w, 's, D, F> { - let last_run = world.last_change_tick(); - let this_run = world.change_tick(); - // SAFETY: We have exclusive access to the entire world. - unsafe { self.query_unchecked_with_ticks(world.as_unsafe_world_cell(), last_run, this_run) } - } - - /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - pub unsafe fn query_unchecked<'w, 's>( - &'s mut self, - world: UnsafeWorldCell<'w>, - ) -> Query<'w, 's, D, F> { - self.update_archetypes_unsafe_world_cell(world); - // SAFETY: Caller ensures we have the required access - unsafe { self.query_unchecked_manual(world) } - } - - /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. - /// - /// This method is slightly more efficient than [`QueryState::query_unchecked`] in some situations, since - /// it does not update this instance's internal cache. The resulting query may skip an entity that - /// belongs to an archetype that has not been cached. - /// - /// To ensure that the cache is up to date, call [`QueryState::update_archetypes`] before this method. - /// The cache is also updated in [`QueryState::new`], [`QueryState::get`], or any method with mutable - /// access to `self`. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - pub unsafe fn query_unchecked_manual<'w, 's>( - &'s self, - world: UnsafeWorldCell<'w>, - ) -> Query<'w, 's, D, F> { - let last_run = world.last_change_tick(); - let this_run = world.change_tick(); - // SAFETY: - // - The caller ensured we have the correct access to the world. - // - The caller ensured that the world matches. - unsafe { self.query_unchecked_manual_with_ticks(world, last_run, this_run) } - } - - /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - pub unsafe fn query_unchecked_with_ticks<'w, 's>( - &'s mut self, - world: UnsafeWorldCell<'w>, - last_run: Tick, - this_run: Tick, - ) -> Query<'w, 's, D, F> { - self.update_archetypes_unsafe_world_cell(world); - // SAFETY: - // - The caller ensured we have the correct access to the world. - // - We called `update_archetypes_unsafe_world_cell`, which calls `validate_world`. - unsafe { self.query_unchecked_manual_with_ticks(world, last_run, this_run) } - } - - /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. - /// - /// This method is slightly more efficient than [`QueryState::query_unchecked_with_ticks`] in some situations, since - /// it does not update this instance's internal cache. The resulting query may skip an entity that - /// belongs to an archetype that has not been cached. - /// - /// To ensure that the cache is up to date, call [`QueryState::update_archetypes`] before this method. - /// The cache is also updated in [`QueryState::new`], [`QueryState::get`], or any method with mutable - /// access to `self`. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - pub unsafe fn query_unchecked_manual_with_ticks<'w, 's>( - &'s self, - world: UnsafeWorldCell<'w>, - last_run: Tick, - this_run: Tick, - ) -> Query<'w, 's, D, F> { - // SAFETY: - // - The caller ensured we have the correct access to the world. - // - The caller ensured that the world matches. - unsafe { Query::new(world, self, last_run, this_run) } - } - - /// Checks if the query is empty for the given [`World`], where the last change and current tick are given. - /// - /// This is equivalent to `self.iter().next().is_none()`, and thus the worst case runtime will be `O(n)` - /// where `n` is the number of *potential* matches. This can be notably expensive for queries that rely - /// on non-archetypal filters such as [`Added`], [`Changed`] or [`Spawned`] which must individually check - /// each query result for a match. - /// - /// # Panics - /// - /// If `world` does not match the one used to call `QueryState::new` for this instance. - /// - /// [`Added`]: crate::query::Added - /// [`Changed`]: crate::query::Changed - /// [`Spawned`]: crate::query::Spawned - #[inline] - pub fn is_empty(&self, world: &World, last_run: Tick, this_run: Tick) -> bool { - self.validate_world(world.id()); - // SAFETY: - // - We have read access to the entire world, and `is_empty()` only performs read access. - // - We called `validate_world`. - unsafe { - self.query_unchecked_manual_with_ticks( - world.as_unsafe_world_cell_readonly(), - last_run, - this_run, - ) - } - .is_empty() - } - - /// Returns `true` if the given [`Entity`] matches the query. - /// - /// This is always guaranteed to run in `O(1)` time. - #[inline] - pub fn contains(&self, entity: Entity, world: &World, last_run: Tick, this_run: Tick) -> bool { - self.validate_world(world.id()); - // SAFETY: - // - We have read access to the entire world, and `is_empty()` only performs read access. - // - We called `validate_world`. - unsafe { - self.query_unchecked_manual_with_ticks( - world.as_unsafe_world_cell_readonly(), - last_run, - this_run, - ) - } - .contains(entity) - } - - /// Updates the state's internal view of the [`World`]'s archetypes. If this is not called before querying data, - /// the results may not accurately reflect what is in the `world`. - /// - /// This is only required if a `manual` method (such as [`Self::get_manual`]) is being called, and it only needs to - /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using - /// non-`manual` methods such as [`QueryState::get`] do not need to call this as it will be automatically called for them. - /// - /// If you have an [`UnsafeWorldCell`] instead of `&World`, consider using [`QueryState::update_archetypes_unsafe_world_cell`]. - /// - /// # Panics - /// - /// If `world` does not match the one used to call `QueryState::new` for this instance. - #[inline] - pub fn update_archetypes(&mut self, world: &World) { - self.update_archetypes_unsafe_world_cell(world.as_unsafe_world_cell_readonly()); - } - - /// Updates the state's internal view of the `world`'s archetypes. If this is not called before querying data, - /// the results may not accurately reflect what is in the `world`. - /// - /// This is only required if a `manual` method (such as [`Self::get_manual`]) is being called, and it only needs to - /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using - /// non-`manual` methods such as [`QueryState::get`] do not need to call this as it will be automatically called for them. - /// - /// # Note - /// - /// This method only accesses world metadata. - /// - /// # Panics - /// - /// If `world` does not match the one used to call `QueryState::new` for this instance. - pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) { - self.validate_world(world.id()); - if self.component_access.required.is_empty() { - let archetypes = world.archetypes(); - let old_generation = - core::mem::replace(&mut self.archetype_generation, archetypes.generation()); - - for archetype in &archetypes[old_generation..] { - // SAFETY: The validate_world call ensures that the world is the same the QueryState - // was initialized from. - unsafe { - self.new_archetype(archetype); - } - } - } else { - // skip if we are already up to date - if self.archetype_generation == world.archetypes().generation() { - return; - } - // if there are required components, we can optimize by only iterating through archetypes - // that contain at least one of the required components - let potential_archetypes = self - .component_access - .required - .ones() - .filter_map(|idx| { - let component_id = ComponentId::get_sparse_set_index(idx); - world - .archetypes() - .component_index() - .get(&component_id) - .map(|index| index.keys()) - }) - // select the component with the fewest archetypes - .min_by_key(ExactSizeIterator::len); - if let Some(archetypes) = potential_archetypes { - for archetype_id in archetypes { - // exclude archetypes that have already been processed - if archetype_id < &self.archetype_generation.0 { - continue; - } - // SAFETY: get_potential_archetypes only returns archetype ids that are valid for the world - let archetype = &world.archetypes()[*archetype_id]; - // SAFETY: The validate_world call ensures that the world is the same the QueryState - // was initialized from. - unsafe { - self.new_archetype(archetype); - } - } - } - self.archetype_generation = world.archetypes().generation(); - } - } - - /// # Panics - /// - /// If `world_id` does not match the [`World`] used to call `QueryState::new` for this instance. - /// - /// Many unsafe query methods require the world to match for soundness. This function is the easiest - /// way of ensuring that it matches. - #[inline] - #[track_caller] - pub fn validate_world(&self, world_id: WorldId) { - #[inline(never)] - #[track_caller] - #[cold] - fn panic_mismatched(this: WorldId, other: WorldId) -> ! { - panic!("Encountered a mismatched World. This QueryState was created from {this:?}, but a method was called using {other:?}."); - } - - if self.world_id != world_id { - panic_mismatched(self.world_id, world_id); - } - } - - /// Update the current [`QueryState`] with information from the provided [`Archetype`] - /// (if applicable, i.e. if the archetype has any intersecting [`ComponentId`] with the current [`QueryState`]). - /// - /// # Safety - /// `archetype` must be from the `World` this state was initialized from. - pub unsafe fn new_archetype(&mut self, archetype: &Archetype) { - if D::matches_component_set(&self.fetch_state, &|id| archetype.contains(id)) - && F::matches_component_set(&self.filter_state, &|id| archetype.contains(id)) - && self.matches_component_set(&|id| archetype.contains(id)) - { - let archetype_index = archetype.id().index(); - if !self.matched_archetypes.contains(archetype_index) { - self.matched_archetypes.grow_and_insert(archetype_index); - if !self.is_dense { - self.matched_storage_ids.push(StorageId { - archetype_id: archetype.id(), - }); - } - } - let table_index = archetype.table_id().as_usize(); - if !self.matched_tables.contains(table_index) { - self.matched_tables.grow_and_insert(table_index); - if self.is_dense { - self.matched_storage_ids.push(StorageId { - table_id: archetype.table_id(), - }); - } - } - } - } - - /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. - pub fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - self.component_access.filter_sets.iter().any(|set| { - set.with - .ones() - .all(|index| set_contains_id(ComponentId::get_sparse_set_index(index))) - && set - .without - .ones() - .all(|index| !set_contains_id(ComponentId::get_sparse_set_index(index))) - }) - } - - /// Use this to transform a [`QueryState`] into a more generic [`QueryState`]. - /// This can be useful for passing to another function that might take the more general form. - /// See [`Query::transmute_lens`](crate::system::Query::transmute_lens) for more details. - /// - /// You should not call [`update_archetypes`](Self::update_archetypes) on the returned [`QueryState`] as the result will be unpredictable. - /// You might end up with a mix of archetypes that only matched the original query + archetypes that only match - /// the new [`QueryState`]. Most of the safe methods on [`QueryState`] call [`QueryState::update_archetypes`] internally, so this - /// best used through a [`Query`] - pub fn transmute<'a, NewD: QueryData>( - &self, - world: impl Into>, - ) -> QueryState { - self.transmute_filtered::(world.into()) - } - - /// Creates a new [`QueryState`] with the same underlying [`FilteredAccess`], matched tables and archetypes - /// as self but with a new type signature. - /// - /// Panics if `NewD` or `NewF` require accesses that this query does not have. - pub fn transmute_filtered<'a, NewD: QueryData, NewF: QueryFilter>( - &self, - world: impl Into>, - ) -> QueryState { - let world = world.into(); - self.validate_world(world.id()); - - let mut component_access = FilteredAccess::default(); - let mut fetch_state = NewD::get_state(world.components()).expect("Could not create fetch_state, Please initialize all referenced components before transmuting."); - let filter_state = NewF::get_state(world.components()).expect("Could not create filter_state, Please initialize all referenced components before transmuting."); - - let mut self_access = self.component_access.clone(); - if D::IS_READ_ONLY { - // The current state was transmuted from a mutable - // `QueryData` to a read-only one. - // Ignore any write access in the current state. - self_access.access_mut().clear_writes(); - } - - NewD::update_component_access(&fetch_state, &mut component_access); - NewD::provide_extra_access( - &mut fetch_state, - component_access.access_mut(), - self_access.access(), - ); - - let mut filter_component_access = FilteredAccess::default(); - NewF::update_component_access(&filter_state, &mut filter_component_access); - - component_access.extend(&filter_component_access); - assert!( - component_access.is_subset(&self_access), - "Transmuted state for {} attempts to access terms that are not allowed by original state {}.", - DebugName::type_name::<(NewD, NewF)>(), DebugName::type_name::<(D, F)>() - ); - - QueryState { - world_id: self.world_id, - archetype_generation: self.archetype_generation, - matched_storage_ids: self.matched_storage_ids.clone(), - is_dense: self.is_dense, - fetch_state, - filter_state, - component_access: self_access, - matched_tables: self.matched_tables.clone(), - matched_archetypes: self.matched_archetypes.clone(), - #[cfg(feature = "trace")] - par_iter_span: tracing::info_span!( - "par_for_each", - query = core::any::type_name::(), - filter = core::any::type_name::(), - ), - } - } - - /// Use this to combine two queries. The data accessed will be the intersection - /// of archetypes included in both queries. This can be useful for accessing a - /// subset of the entities between two queries. - /// - /// You should not call `update_archetypes` on the returned `QueryState` as the result - /// could be unpredictable. You might end up with a mix of archetypes that only matched - /// the original query + archetypes that only match the new `QueryState`. Most of the - /// safe methods on `QueryState` call [`QueryState::update_archetypes`] internally, so - /// this is best used through a `Query`. - /// - /// ## Performance - /// - /// This will have similar performance as constructing a new `QueryState` since much of internal state - /// needs to be reconstructed. But it will be a little faster as it only needs to compare the intersection - /// of matching archetypes rather than iterating over all archetypes. - /// - /// ## Panics - /// - /// Will panic if `NewD` contains accesses not in `Q` or `OtherQ`. - pub fn join<'a, OtherD: QueryData, NewD: QueryData>( - &self, - world: impl Into>, - other: &QueryState, - ) -> QueryState { - self.join_filtered::<_, (), NewD, ()>(world, other) - } - - /// Use this to combine two queries. The data accessed will be the intersection - /// of archetypes included in both queries. - /// - /// ## Panics - /// - /// Will panic if `NewD` or `NewF` requires accesses not in `Q` or `OtherQ`. - pub fn join_filtered< - 'a, - OtherD: QueryData, - OtherF: QueryFilter, - NewD: QueryData, - NewF: QueryFilter, - >( - &self, - world: impl Into>, - other: &QueryState, - ) -> QueryState { - if self.world_id != other.world_id { - panic!("Joining queries initialized on different worlds is not allowed."); - } - - let world = world.into(); - - self.validate_world(world.id()); - - let mut component_access = FilteredAccess::default(); - let mut new_fetch_state = NewD::get_state(world.components()) - .expect("Could not create fetch_state, Please initialize all referenced components before transmuting."); - let new_filter_state = NewF::get_state(world.components()) - .expect("Could not create filter_state, Please initialize all referenced components before transmuting."); - - let mut joined_component_access = self.component_access.clone(); - joined_component_access.extend(&other.component_access); - - if D::IS_READ_ONLY && self.component_access.access().has_any_write() - || OtherD::IS_READ_ONLY && other.component_access.access().has_any_write() - { - // One of the input states was transmuted from a mutable - // `QueryData` to a read-only one. - // Ignore any write access in that current state. - // The simplest way to do this is to clear *all* writes - // and then add back in any writes that are valid - joined_component_access.access_mut().clear_writes(); - if !D::IS_READ_ONLY { - joined_component_access - .access_mut() - .extend(self.component_access.access()); - } - if !OtherD::IS_READ_ONLY { - joined_component_access - .access_mut() - .extend(other.component_access.access()); - } - } - - NewD::update_component_access(&new_fetch_state, &mut component_access); - NewD::provide_extra_access( - &mut new_fetch_state, - component_access.access_mut(), - joined_component_access.access(), - ); - - let mut new_filter_component_access = FilteredAccess::default(); - NewF::update_component_access(&new_filter_state, &mut new_filter_component_access); - - component_access.extend(&new_filter_component_access); - - assert!( - component_access.is_subset(&joined_component_access), - "Joined state for {} attempts to access terms that are not allowed by state {} joined with {}.", - DebugName::type_name::<(NewD, NewF)>(), DebugName::type_name::<(D, F)>(), DebugName::type_name::<(OtherD, OtherF)>() - ); - - if self.archetype_generation != other.archetype_generation { - warn!("You have tried to join queries with different archetype_generations. This could lead to unpredictable results."); - } - - // the join is dense of both the queries were dense. - let is_dense = self.is_dense && other.is_dense; - - // take the intersection of the matched ids - let mut matched_tables = self.matched_tables.clone(); - let mut matched_archetypes = self.matched_archetypes.clone(); - matched_tables.intersect_with(&other.matched_tables); - matched_archetypes.intersect_with(&other.matched_archetypes); - let matched_storage_ids = if is_dense { - matched_tables - .ones() - .map(|id| StorageId { - table_id: TableId::from_usize(id), - }) - .collect() - } else { - matched_archetypes - .ones() - .map(|id| StorageId { - archetype_id: ArchetypeId::new(id), - }) - .collect() - }; - - QueryState { - world_id: self.world_id, - archetype_generation: self.archetype_generation, - matched_storage_ids, - is_dense, - fetch_state: new_fetch_state, - filter_state: new_filter_state, - component_access: joined_component_access, - matched_tables, - matched_archetypes, - #[cfg(feature = "trace")] - par_iter_span: tracing::info_span!( - "par_for_each", - query = core::any::type_name::(), - filter = core::any::type_name::(), - ), - } - } - - /// Gets the query result for the given [`World`] and [`Entity`]. - /// - /// This can only be called for read-only queries, see [`Self::get_mut`] for write-queries. - /// - /// If you need to get multiple items at once but get borrowing errors, - /// consider using [`Self::update_archetypes`] followed by multiple [`Self::get_manual`] calls, - /// or making a single call with [`Self::get_many`] or [`Self::iter_many`]. - /// - /// This is always guaranteed to run in `O(1)` time. - #[inline] - pub fn get<'w>( - &mut self, - world: &'w World, - entity: Entity, - ) -> Result, QueryEntityError> { - self.query(world).get_inner(entity) - } - - /// Returns the read-only query results for the given array of [`Entity`]. - /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is - /// returned instead. - /// - /// Note that the unlike [`QueryState::get_many_mut`], the entities passed in do not need to be unique. - /// - /// # Examples - /// - /// ``` - /// use bevy_ecs::prelude::*; - /// use bevy_ecs::query::QueryEntityError; - /// - /// #[derive(Component, PartialEq, Debug)] - /// struct A(usize); - /// - /// let mut world = World::new(); - /// let entity_vec: Vec = (0..3).map(|i|world.spawn(A(i)).id()).collect(); - /// let entities: [Entity; 3] = entity_vec.try_into().unwrap(); - /// - /// world.spawn(A(73)); - /// - /// let mut query_state = world.query::<&A>(); - /// - /// let component_values = query_state.get_many(&world, entities).unwrap(); - /// - /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); - /// - /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); - /// - /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); - /// ``` - #[inline] - pub fn get_many<'w, const N: usize>( - &mut self, - world: &'w World, - entities: [Entity; N], - ) -> Result<[ROQueryItem<'w, '_, D>; N], QueryEntityError> { - self.query(world).get_many_inner(entities) - } - - /// Returns the read-only query results for the given [`UniqueEntityArray`]. - /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is - /// returned instead. - /// - /// # Examples - /// - /// ``` - /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, UniqueEntityArray, UniqueEntityVec}}; - /// - /// #[derive(Component, PartialEq, Debug)] - /// struct A(usize); - /// - /// let mut world = World::new(); - /// let entity_set: UniqueEntityVec = world.spawn_batch((0..3).map(A)).collect_set(); - /// let entity_set: UniqueEntityArray<3> = entity_set.try_into().unwrap(); - /// - /// world.spawn(A(73)); - /// - /// let mut query_state = world.query::<&A>(); - /// - /// let component_values = query_state.get_many_unique(&world, entity_set).unwrap(); - /// - /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); - /// - /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); - /// - /// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); - /// ``` - #[inline] - pub fn get_many_unique<'w, const N: usize>( - &mut self, - world: &'w World, - entities: UniqueEntityArray, - ) -> Result<[ROQueryItem<'w, '_, D>; N], QueryEntityError> { - self.query(world).get_many_unique_inner(entities) - } - - /// Gets the query result for the given [`World`] and [`Entity`]. - /// - /// This is always guaranteed to run in `O(1)` time. - #[inline] - pub fn get_mut<'w>( - &mut self, - world: &'w mut World, - entity: Entity, - ) -> Result, QueryEntityError> { - self.query_mut(world).get_inner(entity) - } - - /// Returns the query results for the given array of [`Entity`]. - /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is - /// returned instead. - /// - /// ``` - /// use bevy_ecs::prelude::*; - /// use bevy_ecs::query::QueryEntityError; - /// - /// #[derive(Component, PartialEq, Debug)] - /// struct A(usize); - /// - /// let mut world = World::new(); - /// - /// let entities: Vec = (0..3).map(|i|world.spawn(A(i)).id()).collect(); - /// let entities: [Entity; 3] = entities.try_into().unwrap(); - /// - /// world.spawn(A(73)); - /// - /// let mut query_state = world.query::<&mut A>(); - /// - /// let mut mutable_component_values = query_state.get_many_mut(&mut world, entities).unwrap(); - /// - /// for mut a in &mut mutable_component_values { - /// a.0 += 5; - /// } - /// - /// let component_values = query_state.get_many(&world, entities).unwrap(); - /// - /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); - /// - /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); - /// let invalid_entity = world.spawn_empty().id(); - /// - /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); - /// assert_eq!(match query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err() {QueryEntityError::QueryDoesNotMatch(entity, _) => entity, _ => panic!()}, invalid_entity); - /// assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); - /// ``` - #[inline] - pub fn get_many_mut<'w, const N: usize>( - &mut self, - world: &'w mut World, - entities: [Entity; N], - ) -> Result<[D::Item<'w, '_>; N], QueryEntityError> { - self.query_mut(world).get_many_mut_inner(entities) - } - - /// Returns the query results for the given [`UniqueEntityArray`]. - /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is - /// returned instead. - /// - /// ``` - /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, UniqueEntityArray, UniqueEntityVec}}; - /// - /// #[derive(Component, PartialEq, Debug)] - /// struct A(usize); - /// - /// let mut world = World::new(); - /// - /// let entity_set: UniqueEntityVec = world.spawn_batch((0..3).map(A)).collect_set(); - /// let entity_set: UniqueEntityArray<3> = entity_set.try_into().unwrap(); - /// - /// world.spawn(A(73)); - /// - /// let mut query_state = world.query::<&mut A>(); - /// - /// let mut mutable_component_values = query_state.get_many_unique_mut(&mut world, entity_set).unwrap(); - /// - /// for mut a in &mut mutable_component_values { - /// a.0 += 5; - /// } - /// - /// let component_values = query_state.get_many_unique(&world, entity_set).unwrap(); - /// - /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); - /// - /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); - /// let invalid_entity = world.spawn_empty().id(); - /// - /// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); - /// assert_eq!(match query_state.get_many_unique_mut(&mut world, UniqueEntityArray::from([invalid_entity])).unwrap_err() {QueryEntityError::QueryDoesNotMatch(entity, _) => entity, _ => panic!()}, invalid_entity); - /// ``` - #[inline] - pub fn get_many_unique_mut<'w, const N: usize>( - &mut self, - world: &'w mut World, - entities: UniqueEntityArray, - ) -> Result<[D::Item<'w, '_>; N], QueryEntityError> { - self.query_mut(world).get_many_unique_inner(entities) - } - - /// Gets the query result for the given [`World`] and [`Entity`]. - /// - /// This method is slightly more efficient than [`QueryState::get`] in some situations, since - /// it does not update this instance's internal cache. This method will return an error if `entity` - /// belongs to an archetype that has not been cached. - /// - /// To ensure that the cache is up to date, call [`QueryState::update_archetypes`] before this method. - /// The cache is also updated in [`QueryState::new`], `QueryState::get`, or any method with mutable - /// access to `self`. - /// - /// This can only be called for read-only queries, see [`Self::get_mut`] for mutable queries. - /// - /// This is always guaranteed to run in `O(1)` time. - #[inline] - pub fn get_manual<'w>( - &self, - world: &'w World, - entity: Entity, - ) -> Result, QueryEntityError> { - self.query_manual(world).get_inner(entity) - } - - /// Gets the query result for the given [`World`] and [`Entity`]. - /// - /// This is always guaranteed to run in `O(1)` time. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - #[inline] - pub unsafe fn get_unchecked<'w>( - &mut self, - world: UnsafeWorldCell<'w>, - entity: Entity, - ) -> Result, QueryEntityError> { - self.query_unchecked(world).get_inner(entity) - } - - /// Returns an [`Iterator`] over the query results for the given [`World`]. - /// - /// This can only be called for read-only queries, see [`Self::iter_mut`] for write-queries. - /// - /// If you need to iterate multiple times at once but get borrowing errors, - /// consider using [`Self::update_archetypes`] followed by multiple [`Self::iter_manual`] calls. - #[inline] - pub fn iter<'w, 's>(&'s mut self, world: &'w World) -> QueryIter<'w, 's, D::ReadOnly, F> { - self.query(world).into_iter() - } - - /// Returns an [`Iterator`] over the query results for the given [`World`]. - /// - /// This iterator is always guaranteed to return results from each matching entity once and only once. - /// Iteration order is not guaranteed. - #[inline] - pub fn iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryIter<'w, 's, D, F> { - self.query_mut(world).into_iter() - } - - /// Returns an [`Iterator`] over the query results for the given [`World`] without updating the query's archetypes. - /// Archetypes must be manually updated before by using [`Self::update_archetypes`]. - /// - /// This iterator is always guaranteed to return results from each matching entity once and only once. - /// Iteration order is not guaranteed. - /// - /// This can only be called for read-only queries. - #[inline] - pub fn iter_manual<'w, 's>(&'s self, world: &'w World) -> QueryIter<'w, 's, D::ReadOnly, F> { - self.query_manual(world).into_iter() - } - - /// Returns an [`Iterator`] over all possible combinations of `K` query results without repetition. - /// This can only be called for read-only queries. - /// - /// A combination is an arrangement of a collection of items where order does not matter. - /// - /// `K` is the number of items that make up each subset, and the number of items returned by the iterator. - /// `N` is the number of total entities output by query. - /// - /// For example, given the list [1, 2, 3, 4], where `K` is 2, the combinations without repeats are - /// [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]. - /// And in this case, `N` would be defined as 4 since the size of the input list is 4. - /// - /// For combinations of size `K` of query taking `N` inputs, you will get: - /// - if `K == N`: one combination of all query results - /// - if `K < N`: all possible `K`-sized combinations of query results, without repetition - /// - if `K > N`: empty set (no `K`-sized combinations exist) - /// - /// The `iter_combinations` method does not guarantee order of iteration. - /// - /// This iterator is always guaranteed to return results from each unique pair of matching entities. - /// Iteration order is not guaranteed. - /// - /// This can only be called for read-only queries, see [`Self::iter_combinations_mut`] for - /// write-queries. - #[inline] - pub fn iter_combinations<'w, 's, const K: usize>( - &'s mut self, - world: &'w World, - ) -> QueryCombinationIter<'w, 's, D::ReadOnly, F, K> { - self.query(world).iter_combinations_inner() - } - - /// Returns an [`Iterator`] over all possible combinations of `K` query results without repetition. - /// - /// A combination is an arrangement of a collection of items where order does not matter. - /// - /// `K` is the number of items that make up each subset, and the number of items returned by the iterator. - /// `N` is the number of total entities output by query. - /// - /// For example, given the list [1, 2, 3, 4], where `K` is 2, the combinations without repeats are - /// [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]. - /// And in this case, `N` would be defined as 4 since the size of the input list is 4. - /// - /// For combinations of size `K` of query taking `N` inputs, you will get: - /// - if `K == N`: one combination of all query results - /// - if `K < N`: all possible `K`-sized combinations of query results, without repetition - /// - if `K > N`: empty set (no `K`-sized combinations exist) - /// - /// The `iter_combinations_mut` method does not guarantee order of iteration. - #[inline] - pub fn iter_combinations_mut<'w, 's, const K: usize>( - &'s mut self, - world: &'w mut World, - ) -> QueryCombinationIter<'w, 's, D, F, K> { - self.query_mut(world).iter_combinations_inner() - } - - /// Returns an [`Iterator`] over the read-only query items generated from an [`Entity`] list. - /// - /// Items are returned in the order of the list of entities. - /// Entities that don't match the query are skipped. - /// - /// If you need to iterate multiple times at once but get borrowing errors, - /// consider using [`Self::update_archetypes`] followed by multiple [`Self::iter_many_manual`] calls. - /// - /// # See also - /// - /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. - #[inline] - pub fn iter_many<'w, 's, EntityList: IntoIterator>( - &'s mut self, - world: &'w World, - entities: EntityList, - ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { - self.query(world).iter_many_inner(entities) - } - - /// Returns an [`Iterator`] over the read-only query items generated from an [`Entity`] list. - /// - /// Items are returned in the order of the list of entities. - /// Entities that don't match the query are skipped. - /// - /// If `world` archetypes changed since [`Self::update_archetypes`] was last called, - /// this will skip entities contained in new archetypes. - /// - /// This can only be called for read-only queries. - /// - /// # See also - /// - /// - [`iter_many`](Self::iter_many) to update archetypes. - /// - [`iter_manual`](Self::iter_manual) to iterate over all query items. - #[inline] - pub fn iter_many_manual<'w, 's, EntityList: IntoIterator>( - &'s self, - world: &'w World, - entities: EntityList, - ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { - self.query_manual(world).iter_many_inner(entities) - } - - /// Returns an iterator over the query items generated from an [`Entity`] list. - /// - /// Items are returned in the order of the list of entities. - /// Entities that don't match the query are skipped. - #[inline] - pub fn iter_many_mut<'w, 's, EntityList: IntoIterator>( - &'s mut self, - world: &'w mut World, - entities: EntityList, - ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> { - self.query_mut(world).iter_many_inner(entities) - } - - /// Returns an [`Iterator`] over the unique read-only query items generated from an [`EntitySet`]. - /// - /// Items are returned in the order of the list of entities. - /// Entities that don't match the query are skipped. - /// - /// # See also - /// - /// - [`iter_many_unique_mut`](Self::iter_many_unique_mut) to get mutable query items. - #[inline] - pub fn iter_many_unique<'w, 's, EntityList: EntitySet>( - &'s mut self, - world: &'w World, - entities: EntityList, - ) -> QueryManyUniqueIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { - self.query(world).iter_many_unique_inner(entities) - } - - /// Returns an [`Iterator`] over the unique read-only query items generated from an [`EntitySet`]. - /// - /// Items are returned in the order of the list of entities. - /// Entities that don't match the query are skipped. - /// - /// If `world` archetypes changed since [`Self::update_archetypes`] was last called, - /// this will skip entities contained in new archetypes. - /// - /// This can only be called for read-only queries. - /// - /// # See also - /// - /// - [`iter_many_unique`](Self::iter_many) to update archetypes. - /// - [`iter_many`](Self::iter_many) to iterate over a non-unique entity list. - /// - [`iter_manual`](Self::iter_manual) to iterate over all query items. - #[inline] - pub fn iter_many_unique_manual<'w, 's, EntityList: EntitySet>( - &'s self, - world: &'w World, - entities: EntityList, - ) -> QueryManyUniqueIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { - self.query_manual(world).iter_many_unique_inner(entities) - } - - /// Returns an iterator over the unique query items generated from an [`EntitySet`]. - /// - /// Items are returned in the order of the list of entities. - /// Entities that don't match the query are skipped. - #[inline] - pub fn iter_many_unique_mut<'w, 's, EntityList: EntitySet>( - &'s mut self, - world: &'w mut World, - entities: EntityList, - ) -> QueryManyUniqueIter<'w, 's, D, F, EntityList::IntoIter> { - self.query_mut(world).iter_many_unique_inner(entities) - } - /// Returns an [`Iterator`] over the query results for the given [`World`]. - /// - /// This iterator is always guaranteed to return results from each matching entity once and only once. - /// Iteration order is not guaranteed. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - #[inline] - pub unsafe fn iter_unchecked<'w, 's>( - &'s mut self, - world: UnsafeWorldCell<'w>, - ) -> QueryIter<'w, 's, D, F> { - self.query_unchecked(world).into_iter() - } - - /// Returns an [`Iterator`] over all possible combinations of `K` query results for the - /// given [`World`] without repetition. - /// This can only be called for read-only queries. - /// - /// This iterator is always guaranteed to return results from each unique pair of matching entities. - /// Iteration order is not guaranteed. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - #[inline] - pub unsafe fn iter_combinations_unchecked<'w, 's, const K: usize>( - &'s mut self, - world: UnsafeWorldCell<'w>, - ) -> QueryCombinationIter<'w, 's, D, F, K> { - self.query_unchecked(world).iter_combinations_inner() - } - - /// Returns a parallel iterator over the query results for the given [`World`]. - /// - /// This can only be called for read-only queries, see [`par_iter_mut`] for write-queries. - /// - /// Note that you must use the `for_each` method to iterate over the - /// results, see [`par_iter_mut`] for an example. - /// - /// [`par_iter_mut`]: Self::par_iter_mut - #[inline] - pub fn par_iter<'w, 's>( - &'s mut self, - world: &'w World, - ) -> QueryParIter<'w, 's, D::ReadOnly, F> { - self.query(world).par_iter_inner() - } - - /// Returns a parallel iterator over the query results for the given [`World`]. - /// - /// This can only be called for mutable queries, see [`par_iter`] for read-only-queries. - /// - /// # Examples - /// - /// ``` - /// use bevy_ecs::prelude::*; - /// use bevy_ecs::query::QueryEntityError; - /// - /// #[derive(Component, PartialEq, Debug)] - /// struct A(usize); - /// - /// # bevy_tasks::ComputeTaskPool::get_or_init(|| bevy_tasks::TaskPool::new()); - /// - /// let mut world = World::new(); - /// - /// # let entities: Vec = (0..3).map(|i| world.spawn(A(i)).id()).collect(); - /// # let entities: [Entity; 3] = entities.try_into().unwrap(); - /// - /// let mut query_state = world.query::<&mut A>(); - /// - /// query_state.par_iter_mut(&mut world).for_each(|mut a| { - /// a.0 += 5; - /// }); - /// - /// # let component_values = query_state.get_many(&world, entities).unwrap(); - /// - /// # assert_eq!(component_values, [&A(5), &A(6), &A(7)]); - /// - /// # let wrong_entity = Entity::from_raw_u32(57).unwrap(); - /// # let invalid_entity = world.spawn_empty().id(); - /// - /// # assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); - /// assert_eq!(match query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err() {QueryEntityError::QueryDoesNotMatch(entity, _) => entity, _ => panic!()}, invalid_entity); - /// # assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); - /// ``` - /// - /// # Panics - /// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// [`par_iter`]: Self::par_iter - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[inline] - pub fn par_iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryParIter<'w, 's, D, F> { - self.query_mut(world).par_iter_inner() - } - - /// Runs `func` on each query result in parallel for the given [`World`], where the last change and - /// the current change tick are given. This is faster than the equivalent - /// `iter()` method, but cannot be chained like a normal [`Iterator`]. - /// - /// # Panics - /// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - pub(crate) unsafe fn par_fold_init_unchecked_manual<'w, 's, T, FN, INIT>( - &'s self, - init_accum: INIT, - world: UnsafeWorldCell<'w>, - batch_size: u32, - func: FN, - last_run: Tick, - this_run: Tick, - ) where - FN: Fn(T, D::Item<'w, 's>) -> T + Send + Sync + Clone, - INIT: Fn() -> T + Sync + Send + Clone, - { - // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: - // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter,QueryState::par_fold_init_unchecked_manual, - // QueryState::par_many_fold_init_unchecked_manual, QueryState::par_many_unique_fold_init_unchecked_manual - use arrayvec::ArrayVec; - - bevy_tasks::ComputeTaskPool::get().scope(|scope| { - // SAFETY: We only access table data that has been registered in `self.component_access`. - let tables = unsafe { &world.storages().tables }; - let archetypes = world.archetypes(); - let mut batch_queue = ArrayVec::new(); - let mut queue_entity_count = 0; - - // submit a list of storages which smaller than batch_size as single task - let submit_batch_queue = |queue: &mut ArrayVec| { - if queue.is_empty() { - return; - } - let queue = core::mem::take(queue); - let mut func = func.clone(); - let init_accum = init_accum.clone(); - scope.spawn(async move { - #[cfg(feature = "trace")] - let _span = self.par_iter_span.enter(); - let mut iter = self - .query_unchecked_manual_with_ticks(world, last_run, this_run) - .into_iter(); - let mut accum = init_accum(); - for storage_id in queue { - accum = iter.fold_over_storage_range(accum, &mut func, storage_id, None); - } - }); - }; - - // submit single storage larger than batch_size - let submit_single = |count, storage_id: StorageId| { - for offset in (0..count).step_by(batch_size as usize) { - let mut func = func.clone(); - let init_accum = init_accum.clone(); - let len = batch_size.min(count - offset); - let batch = offset..offset + len; - scope.spawn(async move { - #[cfg(feature = "trace")] - let _span = self.par_iter_span.enter(); - let accum = init_accum(); - self.query_unchecked_manual_with_ticks(world, last_run, this_run) - .into_iter() - .fold_over_storage_range(accum, &mut func, storage_id, Some(batch)); - }); - } - }; - - let storage_entity_count = |storage_id: StorageId| -> u32 { - if self.is_dense { - tables[storage_id.table_id].entity_count() - } else { - archetypes[storage_id.archetype_id].len() - } - }; - - for storage_id in &self.matched_storage_ids { - let count = storage_entity_count(*storage_id); - - // skip empty storage - if count == 0 { - continue; - } - // immediately submit large storage - if count >= batch_size { - submit_single(count, *storage_id); - continue; - } - // merge small storage - batch_queue.push(*storage_id); - queue_entity_count += count; - - // submit batch_queue - if queue_entity_count >= batch_size || batch_queue.is_full() { - submit_batch_queue(&mut batch_queue); - queue_entity_count = 0; - } - } - submit_batch_queue(&mut batch_queue); - }); - } - - /// Runs `func` on each query result in parallel for the given [`EntitySet`], - /// where the last change and the current change tick are given. This is faster than the - /// equivalent `iter_many_unique()` method, but cannot be chained like a normal [`Iterator`]. - /// - /// # Panics - /// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - pub(crate) unsafe fn par_many_unique_fold_init_unchecked_manual<'w, 's, T, FN, INIT, E>( - &'s self, - init_accum: INIT, - world: UnsafeWorldCell<'w>, - entity_list: &UniqueEntityEquivalentSlice, - batch_size: u32, - mut func: FN, - last_run: Tick, - this_run: Tick, - ) where - FN: Fn(T, D::Item<'w, 's>) -> T + Send + Sync + Clone, - INIT: Fn() -> T + Sync + Send + Clone, - E: EntityEquivalent + Sync, - { - // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: - // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter,QueryState::par_fold_init_unchecked_manual - // QueryState::par_many_fold_init_unchecked_manual, QueryState::par_many_unique_fold_init_unchecked_manual - - bevy_tasks::ComputeTaskPool::get().scope(|scope| { - let chunks = entity_list.chunks_exact(batch_size as usize); - let remainder = chunks.remainder(); - - for batch in chunks { - let mut func = func.clone(); - let init_accum = init_accum.clone(); - scope.spawn(async move { - #[cfg(feature = "trace")] - let _span = self.par_iter_span.enter(); - let accum = init_accum(); - self.query_unchecked_manual_with_ticks(world, last_run, this_run) - .iter_many_unique_inner(batch) - .fold(accum, &mut func); - }); - } - - #[cfg(feature = "trace")] - let _span = self.par_iter_span.enter(); - let accum = init_accum(); - self.query_unchecked_manual_with_ticks(world, last_run, this_run) - .iter_many_unique_inner(remainder) - .fold(accum, &mut func); - }); - } -} - -impl QueryState { - /// Runs `func` on each read-only query result in parallel for the given [`Entity`] list, - /// where the last change and the current change tick are given. This is faster than the equivalent - /// `iter_many()` method, but cannot be chained like a normal [`Iterator`]. - /// - /// # Panics - /// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being - /// initialized and run from the ECS scheduler, this should never panic. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - /// - /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool - #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - pub(crate) unsafe fn par_many_fold_init_unchecked_manual<'w, 's, T, FN, INIT, E>( - &'s self, - init_accum: INIT, - world: UnsafeWorldCell<'w>, - entity_list: &[E], - batch_size: u32, - mut func: FN, - last_run: Tick, - this_run: Tick, - ) where - FN: Fn(T, D::Item<'w, 's>) -> T + Send + Sync + Clone, - INIT: Fn() -> T + Sync + Send + Clone, - E: EntityEquivalent + Sync, - { - // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: - // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::par_fold_init_unchecked_manual - // QueryState::par_many_fold_init_unchecked_manual, QueryState::par_many_unique_fold_init_unchecked_manual - - bevy_tasks::ComputeTaskPool::get().scope(|scope| { - let chunks = entity_list.chunks_exact(batch_size as usize); - let remainder = chunks.remainder(); - - for batch in chunks { - let mut func = func.clone(); - let init_accum = init_accum.clone(); - scope.spawn(async move { - #[cfg(feature = "trace")] - let _span = self.par_iter_span.enter(); - let accum = init_accum(); - self.query_unchecked_manual_with_ticks(world, last_run, this_run) - .iter_many_inner(batch) - .fold(accum, &mut func); - }); - } - - #[cfg(feature = "trace")] - let _span = self.par_iter_span.enter(); - let accum = init_accum(); - self.query_unchecked_manual_with_ticks(world, last_run, this_run) - .iter_many_inner(remainder) - .fold(accum, &mut func); - }); - } -} - -impl QueryState { - /// Returns a single immutable query result when there is exactly one entity matching - /// the query. - /// - /// This can only be called for read-only queries, - /// see [`single_mut`](Self::single_mut) for write-queries. - /// - /// If the number of query results is not exactly one, a [`QuerySingleError`] is returned - /// instead. - /// - /// # Example - /// - /// Sometimes, you might want to handle the error in a specific way, - /// generally by spawning the missing entity. - /// - /// ```rust - /// use bevy_ecs::prelude::*; - /// use bevy_ecs::query::QuerySingleError; - /// - /// #[derive(Component)] - /// struct A(usize); - /// - /// fn my_system(query: Query<&A>, mut commands: Commands) { - /// match query.single() { - /// Ok(a) => (), // Do something with `a` - /// Err(err) => match err { - /// QuerySingleError::NoEntities(_) => { - /// commands.spawn(A(0)); - /// } - /// QuerySingleError::MultipleEntities(_) => panic!("Multiple entities found!"), - /// }, - /// } - /// } - /// ``` - /// - /// However in most cases, this error can simply be handled with a graceful early return. - /// If this is an expected failure mode, you can do this using the `let else` pattern like so: - /// ```rust - /// use bevy_ecs::prelude::*; - /// - /// #[derive(Component)] - /// struct A(usize); - /// - /// fn my_system(query: Query<&A>) { - /// let Ok(a) = query.single() else { - /// return; - /// }; - /// - /// // Do something with `a` - /// } - /// ``` - /// - /// If this is unexpected though, you should probably use the `?` operator - /// in combination with Bevy's error handling apparatus. - /// - /// ```rust - /// use bevy_ecs::prelude::*; - /// - /// #[derive(Component)] - /// struct A(usize); - /// - /// fn my_system(query: Query<&A>) -> Result { - /// let a = query.single()?; - /// - /// // Do something with `a` - /// Ok(()) - /// } - /// ``` - /// - /// This allows you to globally control how errors are handled in your application, - /// by setting up a custom error handler. - /// See the [`bevy_ecs::error`] module docs for more information! - /// Commonly, you might want to panic on an error during development, but log the error and continue - /// execution in production. - /// - /// Simply unwrapping the [`Result`] also works, but should generally be reserved for tests. - #[inline] - pub fn single<'w>( - &mut self, - world: &'w World, - ) -> Result, QuerySingleError> { - self.query(world).single_inner() - } - - /// Returns a single mutable query result when there is exactly one entity matching - /// the query. - /// - /// If the number of query results is not exactly one, a [`QuerySingleError`] is returned - /// instead. - /// - /// # Examples - /// - /// Please see [`Query::single`] for advice on handling the error. - #[inline] - pub fn single_mut<'w>( - &mut self, - world: &'w mut World, - ) -> Result, QuerySingleError> { - self.query_mut(world).single_inner() - } - - /// Returns a query result when there is exactly one entity matching the query. - /// - /// If the number of query results is not exactly one, a [`QuerySingleError`] is returned - /// instead. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - #[inline] - pub unsafe fn single_unchecked<'w>( - &mut self, - world: UnsafeWorldCell<'w>, - ) -> Result, QuerySingleError> { - self.query_unchecked(world).single_inner() - } - - /// Returns a query result when there is exactly one entity matching the query, - /// where the last change and the current change tick are given. - /// - /// If the number of query results is not exactly one, a [`QuerySingleError`] is returned - /// instead. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - #[inline] - pub unsafe fn single_unchecked_manual<'w>( - &self, - world: UnsafeWorldCell<'w>, - last_run: Tick, - this_run: Tick, - ) -> Result, QuerySingleError> { - // SAFETY: - // - The caller ensured we have the correct access to the world. - // - The caller ensured that the world matches. - self.query_unchecked_manual_with_ticks(world, last_run, this_run) - .single_inner() - } -} - -impl From> for QueryState { - fn from(mut value: QueryBuilder) -> Self { - QueryState::from_builder(&mut value) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - component::Component, - entity_disabling::DefaultQueryFilters, - prelude::*, - system::{QueryLens, RunSystemOnce}, - world::{FilteredEntityMut, FilteredEntityRef}, - }; - - #[test] - #[should_panic] - fn right_world_get() { - let mut world_1 = World::new(); - let world_2 = World::new(); - - let mut query_state = world_1.query::(); - let _panics = query_state.get(&world_2, Entity::from_raw_u32(0).unwrap()); - } - - #[test] - #[should_panic] - fn right_world_get_many() { - let mut world_1 = World::new(); - let world_2 = World::new(); - - let mut query_state = world_1.query::(); - let _panics = query_state.get_many(&world_2, []); - } - - #[test] - #[should_panic] - fn right_world_get_many_mut() { - let mut world_1 = World::new(); - let mut world_2 = World::new(); - - let mut query_state = world_1.query::(); - let _panics = query_state.get_many_mut(&mut world_2, []); - } - - #[derive(Component, PartialEq, Debug)] - struct A(usize); - - #[derive(Component, PartialEq, Debug)] - struct B(usize); - - #[derive(Component, PartialEq, Debug)] - struct C(usize); - - #[test] - fn can_transmute_to_more_general() { - let mut world = World::new(); - world.spawn((A(1), B(0))); - - let query_state = world.query::<(&A, &B)>(); - let mut new_query_state = query_state.transmute::<&A>(&world); - assert_eq!(new_query_state.iter(&world).len(), 1); - let a = new_query_state.single(&world).unwrap(); - - assert_eq!(a.0, 1); - } - - #[test] - fn cannot_get_data_not_in_original_query() { - let mut world = World::new(); - world.spawn((A(0), B(0))); - world.spawn((A(1), B(0), C(0))); - - let query_state = world.query_filtered::<(&A, &B), Without>(); - let mut new_query_state = query_state.transmute::<&A>(&world); - // even though we change the query to not have Without, we do not get the component with C. - let a = new_query_state.single(&world).unwrap(); - - assert_eq!(a.0, 0); - } - - #[test] - fn can_transmute_empty_tuple() { - let mut world = World::new(); - world.register_component::(); - let entity = world.spawn(A(10)).id(); - - let q = world.query::<()>(); - let mut q = q.transmute::(&world); - assert_eq!(q.single(&world).unwrap(), entity); - } - - #[test] - fn can_transmute_immut_fetch() { - let mut world = World::new(); - world.spawn(A(10)); - - let q = world.query::<&A>(); - let mut new_q = q.transmute::>(&world); - assert!(new_q.single(&world).unwrap().is_added()); - - let q = world.query::>(); - let _ = q.transmute::<&A>(&world); - } - - #[test] - fn can_transmute_mut_fetch() { - let mut world = World::new(); - world.spawn(A(0)); - - let q = world.query::<&mut A>(); - let _ = q.transmute::>(&world); - let _ = q.transmute::<&A>(&world); - } - - #[test] - fn can_transmute_entity_mut() { - let mut world = World::new(); - world.spawn(A(0)); - - let q: QueryState> = world.query::(); - let _ = q.transmute::(&world); - } - - #[test] - fn can_generalize_with_option() { - let mut world = World::new(); - world.spawn((A(0), B(0))); - - let query_state = world.query::<(Option<&A>, &B)>(); - let _ = query_state.transmute::>(&world); - let _ = query_state.transmute::<&B>(&world); - } - - #[test] - #[should_panic] - fn cannot_transmute_to_include_data_not_in_original_query() { - let mut world = World::new(); - world.register_component::(); - world.register_component::(); - world.spawn(A(0)); - - let query_state = world.query::<&A>(); - let mut _new_query_state = query_state.transmute::<(&A, &B)>(&world); - } - - #[test] - #[should_panic] - fn cannot_transmute_immut_to_mut() { - let mut world = World::new(); - world.spawn(A(0)); - - let query_state = world.query::<&A>(); - let mut _new_query_state = query_state.transmute::<&mut A>(&world); - } - - #[test] - #[should_panic] - fn cannot_transmute_option_to_immut() { - let mut world = World::new(); - world.spawn(C(0)); - - let query_state = world.query::>(); - let mut new_query_state = query_state.transmute::<&A>(&world); - let x = new_query_state.single(&world).unwrap(); - assert_eq!(x.0, 1234); - } - - #[test] - #[should_panic] - fn cannot_transmute_entity_ref() { - let mut world = World::new(); - world.register_component::(); - - let q = world.query::(); - let _ = q.transmute::<&A>(&world); - } - - #[test] - fn can_transmute_filtered_entity() { - let mut world = World::new(); - let entity = world.spawn((A(0), B(1))).id(); - let query = QueryState::<(Entity, &A, &B)>::new(&mut world) - .transmute::<(Entity, FilteredEntityRef)>(&world); - - let mut query = query; - // Our result is completely untyped - let (_entity, entity_ref) = query.single(&world).unwrap(); - - assert_eq!(entity, entity_ref.id()); - assert_eq!(0, entity_ref.get::().unwrap().0); - assert_eq!(1, entity_ref.get::().unwrap().0); - } - - #[test] - fn can_transmute_added() { - let mut world = World::new(); - let entity_a = world.spawn(A(0)).id(); - - let mut query = QueryState::<(Entity, &A, Has)>::new(&mut world) - .transmute_filtered::<(Entity, Has), Added>(&world); - - assert_eq!((entity_a, false), query.single(&world).unwrap()); - - world.clear_trackers(); - - let entity_b = world.spawn((A(0), B(0))).id(); - assert_eq!((entity_b, true), query.single(&world).unwrap()); - - world.clear_trackers(); - - assert!(query.single(&world).is_err()); - } - - #[test] - fn can_transmute_changed() { - let mut world = World::new(); - let entity_a = world.spawn(A(0)).id(); - - let mut detection_query = QueryState::<(Entity, &A)>::new(&mut world) - .transmute_filtered::>(&world); - - let mut change_query = QueryState::<&mut A>::new(&mut world); - assert_eq!(entity_a, detection_query.single(&world).unwrap()); - - world.clear_trackers(); - - assert!(detection_query.single(&world).is_err()); - - change_query.single_mut(&mut world).unwrap().0 = 1; - - assert_eq!(entity_a, detection_query.single(&world).unwrap()); - } - - #[test] - #[should_panic] - fn cannot_transmute_changed_without_access() { - let mut world = World::new(); - world.register_component::(); - world.register_component::(); - let query = QueryState::<&A>::new(&mut world); - let _new_query = query.transmute_filtered::>(&world); - } - - #[test] - #[should_panic] - fn cannot_transmute_mutable_after_readonly() { - let mut world = World::new(); - // Calling this method would mean we had aliasing queries. - fn bad(_: Query<&mut A>, _: Query<&A>) {} - world - .run_system_once(|query: Query<&mut A>| { - let mut readonly = query.as_readonly(); - let mut lens: QueryLens<&mut A> = readonly.transmute_lens(); - bad(lens.query(), query.as_readonly()); - }) - .unwrap(); - } - - // Regression test for #14629 - #[test] - #[should_panic] - fn transmute_with_different_world() { - let mut world = World::new(); - world.spawn((A(1), B(2))); - - let mut world2 = World::new(); - world2.register_component::(); - - world.query::<(&A, &B)>().transmute::<&B>(&world2); - } - - /// Regression test for issue #14528 - #[test] - fn transmute_from_sparse_to_dense() { - #[derive(Component)] - struct Dense; - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - let mut world = World::new(); - - world.spawn(Dense); - world.spawn((Dense, Sparse)); - - let mut query = world - .query_filtered::<&Dense, With>() - .transmute::<&Dense>(&world); - - let matched = query.iter(&world).count(); - assert_eq!(matched, 1); - } - #[test] - fn transmute_from_dense_to_sparse() { - #[derive(Component)] - struct Dense; - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - let mut world = World::new(); - - world.spawn(Dense); - world.spawn((Dense, Sparse)); - - let mut query = world - .query::<&Dense>() - .transmute_filtered::<&Dense, With>(&world); - - // Note: `transmute_filtered` is supposed to keep the same matched tables/archetypes, - // so it doesn't actually filter out those entities without `Sparse` and the iteration - // remains dense. - let matched = query.iter(&world).count(); - assert_eq!(matched, 2); - } - - #[test] - fn join() { - let mut world = World::new(); - world.spawn(A(0)); - world.spawn(B(1)); - let entity_ab = world.spawn((A(2), B(3))).id(); - world.spawn((A(4), B(5), C(6))); - - let query_1 = QueryState::<&A, Without>::new(&mut world); - let query_2 = QueryState::<&B, Without>::new(&mut world); - let mut new_query: QueryState = query_1.join_filtered(&world, &query_2); - - assert_eq!(new_query.single(&world).unwrap(), entity_ab); - } - - #[test] - fn join_with_get() { - let mut world = World::new(); - world.spawn(A(0)); - world.spawn(B(1)); - let entity_ab = world.spawn((A(2), B(3))).id(); - let entity_abc = world.spawn((A(4), B(5), C(6))).id(); - - let query_1 = QueryState::<&A>::new(&mut world); - let query_2 = QueryState::<&B, Without>::new(&mut world); - let mut new_query: QueryState = query_1.join_filtered(&world, &query_2); - - assert!(new_query.get(&world, entity_ab).is_ok()); - // should not be able to get entity with c. - assert!(new_query.get(&world, entity_abc).is_err()); - } - - #[test] - #[should_panic] - fn cannot_join_wrong_fetch() { - let mut world = World::new(); - world.register_component::(); - let query_1 = QueryState::<&A>::new(&mut world); - let query_2 = QueryState::<&B>::new(&mut world); - let _query: QueryState<&C> = query_1.join(&world, &query_2); - } - - #[test] - #[should_panic] - fn cannot_join_wrong_filter() { - let mut world = World::new(); - let query_1 = QueryState::<&A, Without>::new(&mut world); - let query_2 = QueryState::<&B, Without>::new(&mut world); - let _: QueryState> = query_1.join_filtered(&world, &query_2); - } - - #[test] - #[should_panic] - fn cannot_join_mutable_after_readonly() { - let mut world = World::new(); - // Calling this method would mean we had aliasing queries. - fn bad(_: Query<(&mut A, &mut B)>, _: Query<&A>) {} - world - .run_system_once(|query_a: Query<&mut A>, mut query_b: Query<&mut B>| { - let mut readonly = query_a.as_readonly(); - let mut lens: QueryLens<(&mut A, &mut B)> = readonly.join(&mut query_b); - bad(lens.query(), query_a.as_readonly()); - }) - .unwrap(); - } - - #[test] - fn join_to_filtered_entity_mut() { - let mut world = World::new(); - world.spawn((A(2), B(3))); - - let query_1 = QueryState::<&mut A>::new(&mut world); - let query_2 = QueryState::<&mut B>::new(&mut world); - let mut new_query: QueryState<(Entity, FilteredEntityMut)> = query_1.join(&world, &query_2); - - let (_entity, mut entity_mut) = new_query.single_mut(&mut world).unwrap(); - assert!(entity_mut.get_mut::().is_some()); - assert!(entity_mut.get_mut::().is_some()); - } - - #[test] - fn query_respects_default_filters() { - let mut world = World::new(); - world.spawn((A(0), B(0))); - world.spawn((B(0), C(0))); - world.spawn(C(0)); - - let mut df = DefaultQueryFilters::empty(); - df.register_disabling_component(world.register_component::()); - world.insert_resource(df); - - // Without only matches the first entity - let mut query = QueryState::<()>::new(&mut world); - assert_eq!(1, query.iter(&world).count()); - - // With matches the last two entities - let mut query = QueryState::<(), With>::new(&mut world); - assert_eq!(2, query.iter(&world).count()); - - // Has should bypass the filter entirely - let mut query = QueryState::>::new(&mut world); - assert_eq!(3, query.iter(&world).count()); - - // Allows should bypass the filter entirely - let mut query = QueryState::<(), Allows>::new(&mut world); - assert_eq!(3, query.iter(&world).count()); - - // Other filters should still be respected - let mut query = QueryState::, Without>::new(&mut world); - assert_eq!(1, query.iter(&world).count()); - } - - #[derive(Component)] - struct Table; - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - #[test] - fn query_default_filters_updates_is_dense() { - let mut world = World::new(); - world.spawn((Table, Sparse)); - world.spawn(Table); - world.spawn(Sparse); - - let mut query = QueryState::<()>::new(&mut world); - // There are no sparse components involved thus the query is dense - assert!(query.is_dense); - assert_eq!(3, query.iter(&world).count()); - - let mut df = DefaultQueryFilters::empty(); - df.register_disabling_component(world.register_component::()); - world.insert_resource(df); - - let mut query = QueryState::<()>::new(&mut world); - // The query doesn't ask for sparse components, but the default filters adds - // a sparse components thus it is NOT dense - assert!(!query.is_dense); - assert_eq!(1, query.iter(&world).count()); - - let mut df = DefaultQueryFilters::empty(); - df.register_disabling_component(world.register_component::()); - world.insert_resource(df); - - let mut query = QueryState::<()>::new(&mut world); - // If the filter is instead a table components, the query can still be dense - assert!(query.is_dense); - assert_eq!(1, query.iter(&world).count()); - - let mut query = QueryState::<&Sparse>::new(&mut world); - // But only if the original query was dense - assert!(!query.is_dense); - assert_eq!(1, query.iter(&world).count()); - } -} diff --git a/src/query/world_query.rs b/src/query/world_query.rs deleted file mode 100644 index 1c73992..0000000 --- a/src/query/world_query.rs +++ /dev/null @@ -1,229 +0,0 @@ -use crate::{ - archetype::Archetype, - component::{ComponentId, Components, Tick}, - query::FilteredAccess, - storage::Table, - world::{unsafe_world_cell::UnsafeWorldCell, World}, -}; -use variadics_please::all_tuples; - -/// Types that can be used as parameters in a [`Query`]. -/// Types that implement this should also implement either [`QueryData`] or [`QueryFilter`] -/// -/// # Safety -/// -/// Implementor must ensure that -/// [`update_component_access`], [`QueryData::provide_extra_access`], [`matches_component_set`], [`QueryData::fetch`], [`QueryFilter::filter_fetch`] and [`init_fetch`] -/// obey the following: -/// -/// - For each component mutably accessed by [`QueryData::fetch`], [`update_component_access`] or [`QueryData::provide_extra_access`] should add write access unless read or write access has already been added, in which case it should panic. -/// - For each component readonly accessed by [`QueryData::fetch`] or [`QueryFilter::filter_fetch`], [`update_component_access`] or [`QueryData::provide_extra_access`] should add read access unless write access has already been added, in which case it should panic. -/// - If `fetch` mutably accesses the same component twice, [`update_component_access`] should panic. -/// - [`update_component_access`] may not add a `Without` filter for a component unless [`matches_component_set`] always returns `false` when the component set contains that component. -/// - [`update_component_access`] may not add a `With` filter for a component unless [`matches_component_set`] always returns `false` when the component set doesn't contain that component. -/// - In cases where the query represents a disjunction (such as an `Or` filter) where each element is a valid [`WorldQuery`], the following rules must be obeyed: -/// - [`matches_component_set`] must be a disjunction of the element's implementations -/// - [`update_component_access`] must replace the filters with a disjunction of filters -/// - Each filter in that disjunction must be a conjunction of the corresponding element's filter with the previous `access` -/// - For each resource readonly accessed by [`init_fetch`], [`update_component_access`] should add read access. -/// - Mutable resource access is not allowed. -/// - Any access added during [`QueryData::provide_extra_access`] must be a subset of `available_access`, and must not conflict with any access in `access`. -/// -/// When implementing [`update_component_access`], note that `add_read` and `add_write` both also add a `With` filter, whereas `extend_access` does not change the filters. -/// -/// [`QueryData::provide_extra_access`]: crate::query::QueryData::provide_extra_access -/// [`QueryData::fetch`]: crate::query::QueryData::fetch -/// [`QueryFilter::filter_fetch`]: crate::query::QueryFilter::filter_fetch -/// [`init_fetch`]: Self::init_fetch -/// [`matches_component_set`]: Self::matches_component_set -/// [`Query`]: crate::system::Query -/// [`update_component_access`]: Self::update_component_access -/// [`QueryData`]: crate::query::QueryData -/// [`QueryFilter`]: crate::query::QueryFilter -pub unsafe trait WorldQuery { - /// Per archetype/table state retrieved by this [`WorldQuery`] to compute [`Self::Item`](crate::query::QueryData::Item) for each entity. - type Fetch<'w>: Clone; - - /// State used to construct a [`Self::Fetch`](WorldQuery::Fetch). This will be cached inside [`QueryState`](crate::query::QueryState), - /// so it is best to move as much data / computation here as possible to reduce the cost of - /// constructing [`Self::Fetch`](WorldQuery::Fetch). - type State: Send + Sync + Sized; - - /// This function manually implements subtyping for the query fetches. - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort>; - - /// Creates a new instance of [`Self::Fetch`](WorldQuery::Fetch), - /// by combining data from the [`World`] with the cached [`Self::State`](WorldQuery::State). - /// Readonly accesses resources registered in [`WorldQuery::update_component_access`]. - /// - /// # Safety - /// - /// - `state` must have been initialized (via [`WorldQuery::init_state`]) using the same `world` passed - /// in to this function. - /// - `world` must have the **right** to access any access registered in `update_component_access`. - /// - There must not be simultaneous resource access conflicting with readonly resource access registered in [`WorldQuery::update_component_access`]. - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - state: &'s Self::State, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w>; - - /// Returns true if (and only if) every table of every archetype matched by this fetch contains - /// all of the matched components. - /// - /// This is used to select a more efficient "table iterator" - /// for "dense" queries. If this returns true, [`WorldQuery::set_table`] must be used before - /// [`QueryData::fetch`](crate::query::QueryData::fetch) can be called for iterators. If this returns false, - /// [`WorldQuery::set_archetype`] must be used before [`QueryData::fetch`](crate::query::QueryData::fetch) can be called for - /// iterators. - const IS_DENSE: bool; - - /// Adjusts internal state to account for the next [`Archetype`]. This will always be called on - /// archetypes that match this [`WorldQuery`]. - /// - /// # Safety - /// - /// - `archetype` and `tables` must be from the same [`World`] that [`WorldQuery::init_state`] was called on. - /// - `table` must correspond to `archetype`. - /// - `state` must be the [`State`](Self::State) that `fetch` was initialized with. - unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w>, - state: &'s Self::State, - archetype: &'w Archetype, - table: &'w Table, - ); - - /// Adjusts internal state to account for the next [`Table`]. This will always be called on tables - /// that match this [`WorldQuery`]. - /// - /// # Safety - /// - /// - `table` must be from the same [`World`] that [`WorldQuery::init_state`] was called on. - /// - `state` must be the [`State`](Self::State) that `fetch` was initialized with. - unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w>, - state: &'s Self::State, - table: &'w Table, - ); - - /// Adds any component accesses used by this [`WorldQuery`] to `access`. - /// - /// Used to check which queries are disjoint and can run in parallel - // This does not have a default body of `{}` because 99% of cases need to add accesses - // and forgetting to do so would be unsound. - fn update_component_access(state: &Self::State, access: &mut FilteredAccess); - - /// Creates and initializes a [`State`](WorldQuery::State) for this [`WorldQuery`] type. - fn init_state(world: &mut World) -> Self::State; - - /// Attempts to initialize a [`State`](WorldQuery::State) for this [`WorldQuery`] type using read-only - /// access to [`Components`]. - fn get_state(components: &Components) -> Option; - - /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. - /// - /// Used to check which [`Archetype`]s can be skipped by the query - /// (if none of the [`Component`](crate::component::Component)s match). - /// This is how archetypal query filters like `With` work. - fn matches_component_set( - state: &Self::State, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool; -} - -macro_rules! impl_tuple_world_query { - ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { - - #[expect( - clippy::allow_attributes, - reason = "This is a tuple-related macro; as such the lints below may not always apply." - )] - #[allow( - non_snake_case, - reason = "The names of some variables are provided by the macro's caller, not by us." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." - )] - $(#[$meta])* - /// SAFETY: - /// `fetch` accesses are the conjunction of the subqueries' accesses - /// This is sound because `update_component_access` adds accesses according to the implementations of all the subqueries. - /// `update_component_access` adds all `With` and `Without` filters from the subqueries. - /// This is sound because `matches_component_set` always returns `false` if any the subqueries' implementations return `false`. - unsafe impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) { - type Fetch<'w> = ($($name::Fetch<'w>,)*); - type State = ($($name::State,)*); - - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { - let ($($name,)*) = fetch; - ($( - $name::shrink_fetch($name), - )*) - } - - #[inline] - unsafe fn init_fetch<'w, 's>(world: UnsafeWorldCell<'w>, state: &'s Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> { - let ($($name,)*) = state; - // SAFETY: The invariants are upheld by the caller. - ($(unsafe { $name::init_fetch(world, $name, last_run, this_run) },)*) - } - - const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; - - #[inline] - unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w>, - state: &'s Self::State, - archetype: &'w Archetype, - table: &'w Table - ) { - let ($($name,)*) = fetch; - let ($($state,)*) = state; - // SAFETY: The invariants are upheld by the caller. - $(unsafe { $name::set_archetype($name, $state, archetype, table); })* - } - - #[inline] - unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w>, state: &'s Self::State, table: &'w Table) { - let ($($name,)*) = fetch; - let ($($state,)*) = state; - // SAFETY: The invariants are upheld by the caller. - $(unsafe { $name::set_table($name, $state, table); })* - } - - - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - let ($($name,)*) = state; - $($name::update_component_access($name, access);)* - } - fn init_state(world: &mut World) -> Self::State { - ($($name::init_state(world),)*) - } - fn get_state(components: &Components) -> Option { - Some(($($name::get_state(components)?,)*)) - } - - fn matches_component_set(state: &Self::State, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - let ($($name,)*) = state; - true $(&& $name::matches_component_set($name, set_contains_id))* - } - } - }; -} - -all_tuples!( - #[doc(fake_variadic)] - impl_tuple_world_query, - 0, - 15, - F, - S -); diff --git a/src/schedule/auto_insert_apply_deferred.rs b/src/schedule/auto_insert_apply_deferred.rs deleted file mode 100644 index 524b198..0000000 --- a/src/schedule/auto_insert_apply_deferred.rs +++ /dev/null @@ -1,277 +0,0 @@ -use alloc::{boxed::Box, collections::BTreeSet, vec::Vec}; - -use bevy_platform::collections::HashMap; - -use crate::{ - schedule::{SystemKey, SystemSetKey}, - system::IntoSystem, - world::World, -}; - -use super::{ - is_apply_deferred, ApplyDeferred, DiGraph, Direction, NodeId, ReportCycles, ScheduleBuildError, - ScheduleBuildPass, ScheduleGraph, SystemNode, -}; - -/// A [`ScheduleBuildPass`] that inserts [`ApplyDeferred`] systems into the schedule graph -/// when there are [`Deferred`](crate::prelude::Deferred) -/// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one -/// such deferred buffer. -/// -/// This pass is typically automatically added to the schedule. You can disable this by setting -/// [`ScheduleBuildSettings::auto_insert_apply_deferred`](crate::schedule::ScheduleBuildSettings::auto_insert_apply_deferred) -/// to `false`. You may want to disable this if you only want to sync deferred params at the end of the schedule, -/// or want to manually insert all your sync points. -#[derive(Debug, Default)] -pub struct AutoInsertApplyDeferredPass { - /// Dependency edges that will **not** automatically insert an instance of `ApplyDeferred` on the edge. - no_sync_edges: BTreeSet<(NodeId, NodeId)>, - auto_sync_node_ids: HashMap, -} - -/// If added to a dependency edge, the edge will not be considered for auto sync point insertions. -pub struct IgnoreDeferred; - -impl AutoInsertApplyDeferredPass { - /// Returns the `NodeId` of the cached auto sync point. Will create - /// a new one if needed. - fn get_sync_point(&mut self, graph: &mut ScheduleGraph, distance: u32) -> NodeId { - self.auto_sync_node_ids - .get(&distance) - .copied() - .unwrap_or_else(|| { - let node_id = NodeId::System(self.add_auto_sync(graph)); - self.auto_sync_node_ids.insert(distance, node_id); - node_id - }) - } - /// add an [`ApplyDeferred`] system with no config - fn add_auto_sync(&mut self, graph: &mut ScheduleGraph) -> SystemKey { - let key = graph - .systems - .insert(SystemNode::new(Box::new(IntoSystem::into_system( - ApplyDeferred, - )))); - graph.system_conditions.insert(key, Vec::new()); - - // ignore ambiguities with auto sync points - // They aren't under user control, so no one should know or care. - graph.ambiguous_with_all.insert(NodeId::System(key)); - - key - } -} - -impl ScheduleBuildPass for AutoInsertApplyDeferredPass { - type EdgeOptions = IgnoreDeferred; - - fn add_dependency(&mut self, from: NodeId, to: NodeId, options: Option<&Self::EdgeOptions>) { - if options.is_some() { - self.no_sync_edges.insert((from, to)); - } - } - - fn build( - &mut self, - _world: &mut World, - graph: &mut ScheduleGraph, - dependency_flattened: &mut DiGraph, - ) -> Result<(), ScheduleBuildError> { - let mut sync_point_graph = dependency_flattened.clone(); - let topo = graph.topsort_graph(dependency_flattened, ReportCycles::Dependency)?; - - fn set_has_conditions(graph: &ScheduleGraph, set: SystemSetKey) -> bool { - !graph.set_conditions_at(set).is_empty() - || graph - .hierarchy() - .graph() - .edges_directed(NodeId::Set(set), Direction::Incoming) - .any(|(parent, _)| { - parent - .as_set() - .is_some_and(|p| set_has_conditions(graph, p)) - }) - } - - fn system_has_conditions(graph: &ScheduleGraph, key: SystemKey) -> bool { - !graph.system_conditions[key].is_empty() - || graph - .hierarchy() - .graph() - .edges_directed(NodeId::System(key), Direction::Incoming) - .any(|(parent, _)| { - parent - .as_set() - .is_some_and(|p| set_has_conditions(graph, p)) - }) - } - - let mut system_has_conditions_cache = HashMap::::default(); - let mut is_valid_explicit_sync_point = |key: SystemKey| { - is_apply_deferred(&graph.systems[key].get().unwrap().system) - && !*system_has_conditions_cache - .entry(key) - .or_insert_with(|| system_has_conditions(graph, key)) - }; - - // Calculate the distance for each node. - // The "distance" is the number of sync points between a node and the beginning of the graph. - // Also store if a preceding edge would have added a sync point but was ignored to add it at - // a later edge that is not ignored. - let mut distances_and_pending_sync: HashMap = - HashMap::with_capacity_and_hasher(topo.len(), Default::default()); - - // Keep track of any explicit sync nodes for a specific distance. - let mut distance_to_explicit_sync_node: HashMap = HashMap::default(); - - // Determine the distance for every node and collect the explicit sync points. - for node in &topo { - let &NodeId::System(key) = node else { - panic!("Encountered a non-system node in the flattened dependency graph: {node:?}"); - }; - - let (node_distance, mut node_needs_sync) = distances_and_pending_sync - .get(&key) - .copied() - .unwrap_or_default(); - - if is_valid_explicit_sync_point(key) { - // The distance of this sync point does not change anymore as the iteration order - // makes sure that this node is no unvisited target of another node. - // Because of this, the sync point can be stored for this distance to be reused as - // automatically added sync points later. - distance_to_explicit_sync_node.insert(node_distance, NodeId::System(key)); - - // This node just did a sync, so the only reason to do another sync is if one was - // explicitly scheduled afterwards. - node_needs_sync = false; - } else if !node_needs_sync { - // No previous node has postponed sync points to add so check if the system itself - // has deferred params that require a sync point to apply them. - node_needs_sync = graph.systems[key].get().unwrap().system.has_deferred(); - } - - for target in dependency_flattened.neighbors_directed(*node, Direction::Outgoing) { - let NodeId::System(target) = target else { - panic!("Encountered a non-system node in the flattened dependency graph: {target:?}"); - }; - let (target_distance, target_pending_sync) = - distances_and_pending_sync.entry(target).or_default(); - - let mut edge_needs_sync = node_needs_sync; - if node_needs_sync - && !graph.systems[target].get().unwrap().system.is_exclusive() - && self - .no_sync_edges - .contains(&(*node, NodeId::System(target))) - { - // The node has deferred params to apply, but this edge is ignoring sync points. - // Mark the target as 'delaying' those commands to a future edge and the current - // edge as not needing a sync point. - *target_pending_sync = true; - edge_needs_sync = false; - } - - let mut weight = 0; - if edge_needs_sync || is_valid_explicit_sync_point(target) { - // The target distance grows if a sync point is added between it and the node. - // Also raise the distance if the target is a sync point itself so it then again - // raises the distance of following nodes as that is what the distance is about. - weight = 1; - } - - // The target cannot have fewer sync points in front of it than the preceding node. - *target_distance = (node_distance + weight).max(*target_distance); - } - } - - // Find any edges which have a different number of sync points between them and make sure - // there is a sync point between them. - for node in &topo { - let &NodeId::System(key) = node else { - panic!("Encountered a non-system node in the flattened dependency graph: {node:?}"); - }; - let (node_distance, _) = distances_and_pending_sync - .get(&key) - .copied() - .unwrap_or_default(); - - for target in dependency_flattened.neighbors_directed(*node, Direction::Outgoing) { - let NodeId::System(target) = target else { - panic!("Encountered a non-system node in the flattened dependency graph: {target:?}"); - }; - let (target_distance, _) = distances_and_pending_sync - .get(&target) - .copied() - .unwrap_or_default(); - - if node_distance == target_distance { - // These nodes are the same distance, so they don't need an edge between them. - continue; - } - - if is_apply_deferred(&graph.systems[target].get().unwrap().system) { - // We don't need to insert a sync point since ApplyDeferred is a sync point - // already! - continue; - } - - let sync_point = distance_to_explicit_sync_node - .get(&target_distance) - .copied() - .unwrap_or_else(|| self.get_sync_point(graph, target_distance)); - - sync_point_graph.add_edge(*node, sync_point); - sync_point_graph.add_edge(sync_point, NodeId::System(target)); - - // The edge without the sync point is now redundant. - sync_point_graph.remove_edge(*node, NodeId::System(target)); - } - } - - *dependency_flattened = sync_point_graph; - Ok(()) - } - - fn collapse_set( - &mut self, - set: SystemSetKey, - systems: &[SystemKey], - dependency_flattened: &DiGraph, - ) -> impl Iterator { - if systems.is_empty() { - // collapse dependencies for empty sets - for a in dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Incoming) - { - for b in - dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Outgoing) - { - if self.no_sync_edges.contains(&(a, NodeId::Set(set))) - && self.no_sync_edges.contains(&(NodeId::Set(set), b)) - { - self.no_sync_edges.insert((a, b)); - } - } - } - } else { - for a in dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Incoming) - { - for &sys in systems { - if self.no_sync_edges.contains(&(a, NodeId::Set(set))) { - self.no_sync_edges.insert((a, NodeId::System(sys))); - } - } - } - - for b in dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Outgoing) - { - for &sys in systems { - if self.no_sync_edges.contains(&(NodeId::Set(set), b)) { - self.no_sync_edges.insert((NodeId::System(sys), b)); - } - } - } - } - core::iter::empty() - } -} diff --git a/src/schedule/condition.rs b/src/schedule/condition.rs deleted file mode 100644 index 1a4a7a8..0000000 --- a/src/schedule/condition.rs +++ /dev/null @@ -1,1413 +0,0 @@ -use alloc::{boxed::Box, format}; -use bevy_utils::prelude::DebugName; -use core::ops::Not; - -use crate::system::{ - Adapt, AdapterSystem, CombinatorSystem, Combine, IntoSystem, ReadOnlySystem, RunSystemError, - System, SystemIn, SystemInput, -}; - -/// A type-erased run condition stored in a [`Box`]. -pub type BoxedCondition = Box>; - -/// A system that determines if one or more scheduled systems should run. -/// -/// Implemented for functions and closures that convert into [`System`](System) -/// with [read-only](crate::system::ReadOnlySystemParam) parameters. -/// -/// # Marker type parameter -/// -/// `SystemCondition` trait has `Marker` type parameter, which has no special meaning, -/// but exists to work around the limitation of Rust's trait system. -/// -/// Type parameter in return type can be set to `<()>` by calling [`IntoSystem::into_system`], -/// but usually have to be specified when passing a condition to a function. -/// -/// ``` -/// # use bevy_ecs::schedule::SystemCondition; -/// # use bevy_ecs::system::IntoSystem; -/// fn not_condition(a: impl SystemCondition) -> impl SystemCondition<()> { -/// IntoSystem::into_system(a.map(|x| !x)) -/// } -/// ``` -/// -/// # Examples -/// A condition that returns true every other time it's called. -/// ``` -/// # use bevy_ecs::prelude::*; -/// fn every_other_time() -> impl SystemCondition<()> { -/// IntoSystem::into_system(|mut flag: Local| { -/// *flag = !*flag; -/// *flag -/// }) -/// } -/// -/// # #[derive(Resource)] struct DidRun(bool); -/// # fn my_system(mut did_run: ResMut) { did_run.0 = true; } -/// # let mut schedule = Schedule::default(); -/// schedule.add_systems(my_system.run_if(every_other_time())); -/// # let mut world = World::new(); -/// # world.insert_resource(DidRun(false)); -/// # schedule.run(&mut world); -/// # assert!(world.resource::().0); -/// # world.insert_resource(DidRun(false)); -/// # schedule.run(&mut world); -/// # assert!(!world.resource::().0); -/// ``` -/// -/// A condition that takes a bool as an input and returns it unchanged. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// fn identity() -> impl SystemCondition<(), In> { -/// IntoSystem::into_system(|In(x): In| x) -/// } -/// -/// # fn always_true() -> bool { true } -/// # let mut app = Schedule::default(); -/// # #[derive(Resource)] struct DidRun(bool); -/// # fn my_system(mut did_run: ResMut) { did_run.0 = true; } -/// app.add_systems(my_system.run_if(always_true.pipe(identity()))); -/// # let mut world = World::new(); -/// # world.insert_resource(DidRun(false)); -/// # app.run(&mut world); -/// # assert!(world.resource::().0); -pub trait SystemCondition: - sealed::SystemCondition -{ - /// Returns a new run condition that only returns `true` - /// if both this one and the passed `and` return `true`. - /// - /// The returned run condition is short-circuiting, meaning - /// `and` will only be invoked if `self` returns `true`. - /// - /// # Examples - /// - /// ```should_panic - /// use bevy_ecs::prelude::*; - /// - /// #[derive(Resource, PartialEq)] - /// struct R(u32); - /// - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # fn my_system() {} - /// app.add_systems( - /// // The `resource_equals` run condition will panic since we don't initialize `R`, - /// // just like if we used `Res` in a system. - /// my_system.run_if(resource_equals(R(0))), - /// ); - /// # app.run(&mut world); - /// ``` - /// - /// Use `.and()` to avoid checking the condition. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, PartialEq)] - /// # struct R(u32); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # fn my_system() {} - /// app.add_systems( - /// // `resource_equals` will only get run if the resource `R` exists. - /// my_system.run_if(resource_exists::.and(resource_equals(R(0)))), - /// ); - /// # app.run(&mut world); - /// ``` - /// - /// Note that in this case, it's better to just use the run condition [`resource_exists_and_equals`]. - /// - /// [`resource_exists_and_equals`]: common_conditions::resource_exists_and_equals - fn and>(self, and: C) -> And { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(and); - let name = format!("{} && {}", a.name(), b.name()); - CombinatorSystem::new(a, b, DebugName::owned(name)) - } - - /// Returns a new run condition that only returns `false` - /// if both this one and the passed `nand` return `true`. - /// - /// The returned run condition is short-circuiting, meaning - /// `nand` will only be invoked if `self` returns `true`. - /// - /// # Examples - /// - /// ```compile_fail - /// use bevy::prelude::*; - /// - /// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)] - /// pub enum PlayerState { - /// Alive, - /// Dead, - /// } - /// - /// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)] - /// pub enum EnemyState { - /// Alive, - /// Dead, - /// } - /// - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # fn game_over_credits() {} - /// app.add_systems( - /// // The game_over_credits system will only execute if either the `in_state(PlayerState::Alive)` - /// // run condition or `in_state(EnemyState::Alive)` run condition evaluates to `false`. - /// game_over_credits.run_if( - /// in_state(PlayerState::Alive).nand(in_state(EnemyState::Alive)) - /// ), - /// ); - /// # app.run(&mut world); - /// ``` - /// - /// Equivalent logic can be achieved by using `not` in concert with `and`: - /// - /// ```compile_fail - /// app.add_systems( - /// game_over_credits.run_if( - /// not(in_state(PlayerState::Alive).and(in_state(EnemyState::Alive))) - /// ), - /// ); - /// ``` - fn nand>(self, nand: C) -> Nand { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(nand); - let name = format!("!({} && {})", a.name(), b.name()); - CombinatorSystem::new(a, b, DebugName::owned(name)) - } - - /// Returns a new run condition that only returns `true` - /// if both this one and the passed `nor` return `false`. - /// - /// The returned run condition is short-circuiting, meaning - /// `nor` will only be invoked if `self` returns `false`. - /// - /// # Examples - /// - /// ```compile_fail - /// use bevy::prelude::*; - /// - /// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)] - /// pub enum WeatherState { - /// Sunny, - /// Cloudy, - /// } - /// - /// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)] - /// pub enum SoilState { - /// Fertilized, - /// NotFertilized, - /// } - /// - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # fn slow_plant_growth() {} - /// app.add_systems( - /// // The slow_plant_growth system will only execute if both the `in_state(WeatherState::Sunny)` - /// // run condition and `in_state(SoilState::Fertilized)` run condition evaluate to `false`. - /// slow_plant_growth.run_if( - /// in_state(WeatherState::Sunny).nor(in_state(SoilState::Fertilized)) - /// ), - /// ); - /// # app.run(&mut world); - /// ``` - /// - /// Equivalent logic can be achieved by using `not` in concert with `or`: - /// - /// ```compile_fail - /// app.add_systems( - /// slow_plant_growth.run_if( - /// not(in_state(WeatherState::Sunny).or(in_state(SoilState::Fertilized))) - /// ), - /// ); - /// ``` - fn nor>(self, nor: C) -> Nor { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(nor); - let name = format!("!({} || {})", a.name(), b.name()); - CombinatorSystem::new(a, b, DebugName::owned(name)) - } - - /// Returns a new run condition that returns `true` - /// if either this one or the passed `or` return `true`. - /// - /// The returned run condition is short-circuiting, meaning - /// `or` will only be invoked if `self` returns `false`. - /// - /// # Examples - /// - /// ``` - /// use bevy_ecs::prelude::*; - /// - /// #[derive(Resource, PartialEq)] - /// struct A(u32); - /// - /// #[derive(Resource, PartialEq)] - /// struct B(u32); - /// - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # #[derive(Resource)] struct C(bool); - /// # fn my_system(mut c: ResMut) { c.0 = true; } - /// app.add_systems( - /// // Only run the system if either `A` or `B` exist. - /// my_system.run_if(resource_exists::.or(resource_exists::)), - /// ); - /// # - /// # world.insert_resource(C(false)); - /// # app.run(&mut world); - /// # assert!(!world.resource::().0); - /// # - /// # world.insert_resource(A(0)); - /// # app.run(&mut world); - /// # assert!(world.resource::().0); - /// # - /// # world.remove_resource::(); - /// # world.insert_resource(B(0)); - /// # world.insert_resource(C(false)); - /// # app.run(&mut world); - /// # assert!(world.resource::().0); - /// ``` - fn or>(self, or: C) -> Or { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(or); - let name = format!("{} || {}", a.name(), b.name()); - CombinatorSystem::new(a, b, DebugName::owned(name)) - } - - /// Returns a new run condition that only returns `true` - /// if `self` and `xnor` **both** return `false` or **both** return `true`. - /// - /// # Examples - /// - /// ```compile_fail - /// use bevy::prelude::*; - /// - /// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)] - /// pub enum CoffeeMachineState { - /// Heating, - /// Brewing, - /// Inactive, - /// } - /// - /// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)] - /// pub enum TeaKettleState { - /// Heating, - /// Steeping, - /// Inactive, - /// } - /// - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # fn take_drink_orders() {} - /// app.add_systems( - /// // The take_drink_orders system will only execute if the `in_state(CoffeeMachineState::Inactive)` - /// // run condition and `in_state(TeaKettleState::Inactive)` run conditions both evaluate to `false`, - /// // or both evaluate to `true`. - /// take_drink_orders.run_if( - /// in_state(CoffeeMachineState::Inactive).xnor(in_state(TeaKettleState::Inactive)) - /// ), - /// ); - /// # app.run(&mut world); - /// ``` - /// - /// Equivalent logic can be achieved by using `not` in concert with `xor`: - /// - /// ```compile_fail - /// app.add_systems( - /// take_drink_orders.run_if( - /// not(in_state(CoffeeMachineState::Inactive).xor(in_state(TeaKettleState::Inactive))) - /// ), - /// ); - /// ``` - fn xnor>(self, xnor: C) -> Xnor { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(xnor); - let name = format!("!({} ^ {})", a.name(), b.name()); - CombinatorSystem::new(a, b, DebugName::owned(name)) - } - - /// Returns a new run condition that only returns `true` - /// if either `self` or `xor` return `true`, but not both. - /// - /// # Examples - /// - /// ```compile_fail - /// use bevy::prelude::*; - /// - /// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)] - /// pub enum CoffeeMachineState { - /// Heating, - /// Brewing, - /// Inactive, - /// } - /// - /// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)] - /// pub enum TeaKettleState { - /// Heating, - /// Steeping, - /// Inactive, - /// } - /// - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # fn prepare_beverage() {} - /// app.add_systems( - /// // The prepare_beverage system will only execute if either the `in_state(CoffeeMachineState::Inactive)` - /// // run condition or `in_state(TeaKettleState::Inactive)` run condition evaluates to `true`, - /// // but not both. - /// prepare_beverage.run_if( - /// in_state(CoffeeMachineState::Inactive).xor(in_state(TeaKettleState::Inactive)) - /// ), - /// ); - /// # app.run(&mut world); - /// ``` - fn xor>(self, xor: C) -> Xor { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(xor); - let name = format!("({} ^ {})", a.name(), b.name()); - CombinatorSystem::new(a, b, DebugName::owned(name)) - } -} - -impl SystemCondition for F where - F: sealed::SystemCondition -{ -} - -mod sealed { - use crate::system::{IntoSystem, ReadOnlySystem, SystemInput}; - - pub trait SystemCondition: - IntoSystem - { - // This associated type is necessary to let the compiler - // know that `Self::System` is `ReadOnlySystem`. - type ReadOnlySystem: ReadOnlySystem; - } - - impl SystemCondition for F - where - F: IntoSystem, - F::System: ReadOnlySystem, - { - type ReadOnlySystem = F::System; - } -} - -/// A collection of [run conditions](SystemCondition) that may be useful in any bevy app. -pub mod common_conditions { - use super::{NotSystem, SystemCondition}; - use crate::{ - change_detection::DetectChanges, - event::{BufferedEvent, EventReader}, - lifecycle::RemovedComponents, - prelude::{Component, Query, With}, - query::QueryFilter, - resource::Resource, - system::{In, IntoSystem, Local, Res, System, SystemInput}, - }; - use alloc::format; - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// on the first time the condition is run and false every time after. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// app.add_systems( - /// // `run_once` will only return true the first time it's evaluated - /// my_system.run_if(run_once), - /// ); - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// // This is the first time the condition will be evaluated so `my_system` will run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// - /// // This is the seconds time the condition will be evaluated so `my_system` won't run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// ``` - pub fn run_once(mut has_run: Local) -> bool { - if !*has_run { - *has_run = true; - true - } else { - false - } - } - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// if the resource exists. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// app.add_systems( - /// // `resource_exists` will only return true if the given resource exists in the world - /// my_system.run_if(resource_exists::), - /// ); - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// // `Counter` hasn't been added so `my_system` won't run - /// app.run(&mut world); - /// world.init_resource::(); - /// - /// // `Counter` has now been added so `my_system` can run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// ``` - pub fn resource_exists(res: Option>) -> bool - where - T: Resource, - { - res.is_some() - } - - /// Generates a [`SystemCondition`]-satisfying closure that returns `true` - /// if the resource is equal to `value`. - /// - /// # Panics - /// - /// The condition will panic if the resource does not exist. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default, PartialEq)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// app.add_systems( - /// // `resource_equals` will only return true if the given resource equals the given value - /// my_system.run_if(resource_equals(Counter(0))), - /// ); - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// // `Counter` is `0` so `my_system` can run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// - /// // `Counter` is no longer `0` so `my_system` won't run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// ``` - pub fn resource_equals(value: T) -> impl FnMut(Res) -> bool - where - T: Resource + PartialEq, - { - move |res: Res| *res == value - } - - /// Generates a [`SystemCondition`]-satisfying closure that returns `true` - /// if the resource exists and is equal to `value`. - /// - /// The condition will return `false` if the resource does not exist. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default, PartialEq)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// app.add_systems( - /// // `resource_exists_and_equals` will only return true - /// // if the given resource exists and equals the given value - /// my_system.run_if(resource_exists_and_equals(Counter(0))), - /// ); - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// // `Counter` hasn't been added so `my_system` can't run - /// app.run(&mut world); - /// world.init_resource::(); - /// - /// // `Counter` is `0` so `my_system` can run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// - /// // `Counter` is no longer `0` so `my_system` won't run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// ``` - pub fn resource_exists_and_equals(value: T) -> impl FnMut(Option>) -> bool - where - T: Resource + PartialEq, - { - move |res: Option>| match res { - Some(res) => *res == value, - None => false, - } - } - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// if the resource of the given type has been added since the condition was last checked. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// app.add_systems( - /// // `resource_added` will only return true if the - /// // given resource was just added - /// my_system.run_if(resource_added::), - /// ); - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// world.init_resource::(); - /// - /// // `Counter` was just added so `my_system` will run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// - /// // `Counter` was not just added so `my_system` will not run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// ``` - pub fn resource_added(res: Option>) -> bool - where - T: Resource, - { - match res { - Some(res) => res.is_added(), - None => false, - } - } - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// if the resource of the given type has been added or mutably dereferenced - /// since the condition was last checked. - /// - /// **Note** that simply *mutably dereferencing* a resource is considered a change ([`DerefMut`](std::ops::DerefMut)). - /// Bevy does not compare resources to their previous values. - /// - /// # Panics - /// - /// The condition will panic if the resource does not exist. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// app.add_systems( - /// // `resource_changed` will only return true if the - /// // given resource was just changed (or added) - /// my_system.run_if( - /// resource_changed:: - /// // By default detecting changes will also trigger if the resource was - /// // just added, this won't work with my example so I will add a second - /// // condition to make sure the resource wasn't just added - /// .and(not(resource_added::)) - /// ), - /// ); - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// // `Counter` hasn't been changed so `my_system` won't run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 0); - /// - /// world.resource_mut::().0 = 50; - /// - /// // `Counter` was just changed so `my_system` will run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 51); - /// ``` - pub fn resource_changed(res: Res) -> bool - where - T: Resource, - { - res.is_changed() - } - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// if the resource of the given type has been added or mutably dereferenced since the condition - /// was last checked. - /// - /// **Note** that simply *mutably dereferencing* a resource is considered a change ([`DerefMut`](std::ops::DerefMut)). - /// Bevy does not compare resources to their previous values. - /// - /// The condition will return `false` if the resource does not exist. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// app.add_systems( - /// // `resource_exists_and_changed` will only return true if the - /// // given resource exists and was just changed (or added) - /// my_system.run_if( - /// resource_exists_and_changed:: - /// // By default detecting changes will also trigger if the resource was - /// // just added, this won't work with my example so I will add a second - /// // condition to make sure the resource wasn't just added - /// .and(not(resource_added::)) - /// ), - /// ); - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// // `Counter` doesn't exist so `my_system` won't run - /// app.run(&mut world); - /// world.init_resource::(); - /// - /// // `Counter` hasn't been changed so `my_system` won't run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 0); - /// - /// world.resource_mut::().0 = 50; - /// - /// // `Counter` was just changed so `my_system` will run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 51); - /// ``` - pub fn resource_exists_and_changed(res: Option>) -> bool - where - T: Resource, - { - match res { - Some(res) => res.is_changed(), - None => false, - } - } - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// if the resource of the given type has been added, removed or mutably dereferenced since the condition - /// was last checked. - /// - /// **Note** that simply *mutably dereferencing* a resource is considered a change ([`DerefMut`](std::ops::DerefMut)). - /// Bevy does not compare resources to their previous values. - /// - /// The condition will return `false` if the resource does not exist. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// app.add_systems( - /// // `resource_changed_or_removed` will only return true if the - /// // given resource was just changed or removed (or added) - /// my_system.run_if( - /// resource_changed_or_removed:: - /// // By default detecting changes will also trigger if the resource was - /// // just added, this won't work with my example so I will add a second - /// // condition to make sure the resource wasn't just added - /// .and(not(resource_added::)) - /// ), - /// ); - /// - /// #[derive(Resource, Default)] - /// struct MyResource; - /// - /// // If `Counter` exists, increment it, otherwise insert `MyResource` - /// fn my_system(mut commands: Commands, mut counter: Option>) { - /// if let Some(mut counter) = counter { - /// counter.0 += 1; - /// } else { - /// commands.init_resource::(); - /// } - /// } - /// - /// // `Counter` hasn't been changed so `my_system` won't run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 0); - /// - /// world.resource_mut::().0 = 50; - /// - /// // `Counter` was just changed so `my_system` will run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 51); - /// - /// world.remove_resource::(); - /// - /// // `Counter` was just removed so `my_system` will run - /// app.run(&mut world); - /// assert_eq!(world.contains_resource::(), true); - /// ``` - pub fn resource_changed_or_removed(res: Option>, mut existed: Local) -> bool - where - T: Resource, - { - if let Some(value) = res { - *existed = true; - value.is_changed() - } else if *existed { - *existed = false; - true - } else { - false - } - } - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// if the resource of the given type has been removed since the condition was last checked. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// app.add_systems( - /// // `resource_removed` will only return true if the - /// // given resource was just removed - /// my_system.run_if(resource_removed::), - /// ); - /// - /// #[derive(Resource, Default)] - /// struct MyResource; - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// world.init_resource::(); - /// - /// // `MyResource` hasn't just been removed so `my_system` won't run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 0); - /// - /// world.remove_resource::(); - /// - /// // `MyResource` was just removed so `my_system` will run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// ``` - pub fn resource_removed(res: Option>, mut existed: Local) -> bool - where - T: Resource, - { - if res.is_some() { - *existed = true; - false - } else if *existed { - *existed = false; - true - } else { - false - } - } - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// if there are any new events of the given type since it was last called. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// # world.init_resource::>(); - /// # app.add_systems(bevy_ecs::event::event_update_system.before(my_system)); - /// - /// app.add_systems( - /// my_system.run_if(on_event::), - /// ); - /// - /// #[derive(Event, BufferedEvent)] - /// struct MyEvent; - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// // No new `MyEvent` events have been push so `my_system` won't run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 0); - /// - /// world.resource_mut::>().send(MyEvent); - /// - /// // A `MyEvent` event has been pushed so `my_system` will run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// ``` - pub fn on_event(mut reader: EventReader) -> bool { - // The events need to be consumed, so that there are no false positives on subsequent - // calls of the run condition. Simply checking `is_empty` would not be enough. - // PERF: note that `count` is efficient (not actually looping/iterating), - // due to Bevy having a specialized implementation for events. - reader.read().count() > 0 - } - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// if there are any entities with the given component type. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// app.add_systems( - /// my_system.run_if(any_with_component::), - /// ); - /// - /// #[derive(Component)] - /// struct MyComponent; - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// // No entities exist yet with a `MyComponent` component so `my_system` won't run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 0); - /// - /// world.spawn(MyComponent); - /// - /// // An entities with `MyComponent` now exists so `my_system` will run - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// ``` - pub fn any_with_component(query: Query<(), With>) -> bool { - !query.is_empty() - } - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// if there are any entity with a component of the given type removed. - pub fn any_component_removed(mut removals: RemovedComponents) -> bool { - // `RemovedComponents` based on events and therefore events need to be consumed, - // so that there are no false positives on subsequent calls of the run condition. - // Simply checking `is_empty` would not be enough. - // PERF: note that `count` is efficient (not actually looping/iterating), - // due to Bevy having a specialized implementation for events. - removals.read().count() > 0 - } - - /// A [`SystemCondition`]-satisfying system that returns `true` - /// if there are any entities that match the given [`QueryFilter`]. - pub fn any_match_filter(query: Query<(), F>) -> bool { - !query.is_empty() - } - - /// Generates a [`SystemCondition`] that inverses the result of passed one. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// app.add_systems( - /// // `not` will inverse any condition you pass in. - /// // Since the condition we choose always returns true - /// // this system will never run - /// my_system.run_if(not(always)), - /// ); - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// fn always() -> bool { - /// true - /// } - /// - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 0); - /// ``` - pub fn not(condition: T) -> NotSystem - where - TOut: core::ops::Not, - T: IntoSystem<(), TOut, Marker>, - { - let condition = IntoSystem::into_system(condition); - let name = format!("!{}", condition.name()); - NotSystem::new(super::NotMarker, condition, name.into()) - } - - /// Generates a [`SystemCondition`] that returns true when the passed one changes. - /// - /// The first time this is called, the passed condition is assumed to have been previously false. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// app.add_systems( - /// my_system.run_if(condition_changed(resource_exists::)), - /// ); - /// - /// #[derive(Resource)] - /// struct MyResource; - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// // `MyResource` is initially there, the inner condition is true, the system runs once - /// world.insert_resource(MyResource); - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// - /// // We remove `MyResource`, the inner condition is now false, the system runs one more time. - /// world.remove_resource::(); - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 2); - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 2); - /// ``` - pub fn condition_changed(condition: C) -> impl SystemCondition<(), CIn> - where - CIn: SystemInput, - C: SystemCondition, - { - IntoSystem::into_system(condition.pipe(|In(new): In, mut prev: Local| { - let changed = *prev != new; - *prev = new; - changed - })) - } - - /// Generates a [`SystemCondition`] that returns true when the result of - /// the passed one went from false to true since the last time this was called. - /// - /// The first time this is called, the passed condition is assumed to have been previously false. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource, Default)] - /// # struct Counter(u8); - /// # let mut app = Schedule::default(); - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// app.add_systems( - /// my_system.run_if(condition_changed_to(true, resource_exists::)), - /// ); - /// - /// #[derive(Resource)] - /// struct MyResource; - /// - /// fn my_system(mut counter: ResMut) { - /// counter.0 += 1; - /// } - /// - /// // `MyResource` is initially there, the inner condition is true, the system runs once - /// world.insert_resource(MyResource); - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// - /// // We remove `MyResource`, the inner condition is now false, the system doesn't run. - /// world.remove_resource::(); - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 1); - /// - /// // We reinsert `MyResource` again, so the system will run one more time - /// world.insert_resource(MyResource); - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 2); - /// app.run(&mut world); - /// assert_eq!(world.resource::().0, 2); - /// ``` - pub fn condition_changed_to( - to: bool, - condition: C, - ) -> impl SystemCondition<(), CIn> - where - CIn: SystemInput, - C: SystemCondition, - { - IntoSystem::into_system(condition.pipe( - move |In(new): In, mut prev: Local| -> bool { - let now_true = *prev != new && new == to; - *prev = new; - now_true - }, - )) - } -} - -/// Invokes [`Not`] with the output of another system. -/// -/// See [`common_conditions::not`] for examples. -pub type NotSystem = AdapterSystem; - -/// Used with [`AdapterSystem`] to negate the output of a system via the [`Not`] operator. -#[doc(hidden)] -#[derive(Clone, Copy)] -pub struct NotMarker; - -impl> Adapt for NotMarker { - type In = S::In; - type Out = ::Output; - - fn adapt( - &mut self, - input: ::Inner<'_>, - run_system: impl FnOnce(SystemIn<'_, S>) -> Result, - ) -> Result { - run_system(input).map(Not::not) - } -} - -/// Combines the outputs of two systems using the `&&` operator. -pub type And = CombinatorSystem; - -/// Combines and inverts the outputs of two systems using the `&&` and `!` operators. -pub type Nand = CombinatorSystem; - -/// Combines and inverts the outputs of two systems using the `&&` and `!` operators. -pub type Nor = CombinatorSystem; - -/// Combines the outputs of two systems using the `||` operator. -pub type Or = CombinatorSystem; - -/// Combines and inverts the outputs of two systems using the `^` and `!` operators. -pub type Xnor = CombinatorSystem; - -/// Combines the outputs of two systems using the `^` operator. -pub type Xor = CombinatorSystem; - -#[doc(hidden)] -pub struct AndMarker; - -impl Combine for AndMarker -where - for<'a> In: SystemInput: Copy>, - A: System, - B: System, -{ - type In = In; - type Out = bool; - - fn combine( - input: ::Inner<'_>, - a: impl FnOnce(SystemIn<'_, A>) -> Result, - b: impl FnOnce(SystemIn<'_, A>) -> Result, - ) -> Result { - Ok(a(input)? && b(input)?) - } -} - -#[doc(hidden)] -pub struct NandMarker; - -impl Combine for NandMarker -where - for<'a> In: SystemInput: Copy>, - A: System, - B: System, -{ - type In = In; - type Out = bool; - - fn combine( - input: ::Inner<'_>, - a: impl FnOnce(SystemIn<'_, A>) -> Result, - b: impl FnOnce(SystemIn<'_, A>) -> Result, - ) -> Result { - Ok(!(a(input)? && b(input)?)) - } -} - -#[doc(hidden)] -pub struct NorMarker; - -impl Combine for NorMarker -where - for<'a> In: SystemInput: Copy>, - A: System, - B: System, -{ - type In = In; - type Out = bool; - - fn combine( - input: ::Inner<'_>, - a: impl FnOnce(SystemIn<'_, A>) -> Result, - b: impl FnOnce(SystemIn<'_, A>) -> Result, - ) -> Result { - Ok(!(a(input)? || b(input)?)) - } -} - -#[doc(hidden)] -pub struct OrMarker; - -impl Combine for OrMarker -where - for<'a> In: SystemInput: Copy>, - A: System, - B: System, -{ - type In = In; - type Out = bool; - - fn combine( - input: ::Inner<'_>, - a: impl FnOnce(SystemIn<'_, A>) -> Result, - b: impl FnOnce(SystemIn<'_, A>) -> Result, - ) -> Result { - Ok(a(input)? || b(input)?) - } -} - -#[doc(hidden)] -pub struct XnorMarker; - -impl Combine for XnorMarker -where - for<'a> In: SystemInput: Copy>, - A: System, - B: System, -{ - type In = In; - type Out = bool; - - fn combine( - input: ::Inner<'_>, - a: impl FnOnce(SystemIn<'_, A>) -> Result, - b: impl FnOnce(SystemIn<'_, A>) -> Result, - ) -> Result { - Ok(!(a(input)? ^ b(input)?)) - } -} - -#[doc(hidden)] -pub struct XorMarker; - -impl Combine for XorMarker -where - for<'a> In: SystemInput: Copy>, - A: System, - B: System, -{ - type In = In; - type Out = bool; - - fn combine( - input: ::Inner<'_>, - a: impl FnOnce(SystemIn<'_, A>) -> Result, - b: impl FnOnce(SystemIn<'_, A>) -> Result, - ) -> Result { - Ok(a(input)? ^ b(input)?) - } -} - -#[cfg(test)] -mod tests { - use super::{common_conditions::*, SystemCondition}; - use crate::event::{BufferedEvent, Event}; - use crate::query::With; - use crate::{ - change_detection::ResMut, - component::Component, - schedule::{IntoScheduleConfigs, Schedule}, - system::Local, - world::World, - }; - use bevy_ecs_macros::Resource; - - #[derive(Resource, Default)] - struct Counter(usize); - - fn increment_counter(mut counter: ResMut) { - counter.0 += 1; - } - - fn double_counter(mut counter: ResMut) { - counter.0 *= 2; - } - - fn every_other_time(mut has_ran: Local) -> bool { - *has_ran = !*has_ran; - *has_ran - } - - #[test] - fn run_condition() { - let mut world = World::new(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - // Run every other cycle - schedule.add_systems(increment_counter.run_if(every_other_time)); - - schedule.run(&mut world); - schedule.run(&mut world); - assert_eq!(world.resource::().0, 1); - schedule.run(&mut world); - schedule.run(&mut world); - assert_eq!(world.resource::().0, 2); - - // Run every other cycle opposite to the last one - schedule.add_systems(increment_counter.run_if(not(every_other_time))); - - schedule.run(&mut world); - schedule.run(&mut world); - assert_eq!(world.resource::().0, 4); - schedule.run(&mut world); - schedule.run(&mut world); - assert_eq!(world.resource::().0, 6); - } - - #[test] - fn run_condition_combinators() { - let mut world = World::new(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - schedule.add_systems( - ( - increment_counter.run_if(every_other_time.and(|| true)), // Run every odd cycle. - increment_counter.run_if(every_other_time.nand(|| false)), // Always run. - double_counter.run_if(every_other_time.nor(|| false)), // Run every even cycle. - increment_counter.run_if(every_other_time.or(|| true)), // Always run. - increment_counter.run_if(every_other_time.xnor(|| true)), // Run every odd cycle. - double_counter.run_if(every_other_time.xnor(|| false)), // Run every even cycle. - increment_counter.run_if(every_other_time.xor(|| false)), // Run every odd cycle. - double_counter.run_if(every_other_time.xor(|| true)), // Run every even cycle. - ) - .chain(), - ); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, 5); - schedule.run(&mut world); - assert_eq!(world.resource::().0, 52); - } - - #[test] - fn multiple_run_conditions() { - let mut world = World::new(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - // Run every other cycle - schedule.add_systems(increment_counter.run_if(every_other_time).run_if(|| true)); - // Never run - schedule.add_systems(increment_counter.run_if(every_other_time).run_if(|| false)); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, 1); - schedule.run(&mut world); - assert_eq!(world.resource::().0, 1); - } - - #[test] - fn multiple_run_conditions_is_and_operation() { - let mut world = World::new(); - world.init_resource::(); - - let mut schedule = Schedule::default(); - - // This should never run, if multiple run conditions worked - // like an OR condition then it would always run - schedule.add_systems( - increment_counter - .run_if(every_other_time) - .run_if(not(every_other_time)), - ); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, 0); - schedule.run(&mut world); - assert_eq!(world.resource::().0, 0); - } - #[derive(Component)] - struct TestComponent; - - #[derive(Event, BufferedEvent)] - struct TestEvent; - - #[derive(Resource)] - struct TestResource(()); - - fn test_system() {} - - // Ensure distributive_run_if compiles with the common conditions. - #[test] - fn distributive_run_if_compiles() { - Schedule::default().add_systems( - (test_system, test_system) - .distributive_run_if(run_once) - .distributive_run_if(resource_exists::) - .distributive_run_if(resource_added::) - .distributive_run_if(resource_changed::) - .distributive_run_if(resource_exists_and_changed::) - .distributive_run_if(resource_changed_or_removed::) - .distributive_run_if(resource_removed::) - .distributive_run_if(on_event::) - .distributive_run_if(any_with_component::) - .distributive_run_if(any_match_filter::>) - .distributive_run_if(not(run_once)), - ); - } -} diff --git a/src/schedule/config.rs b/src/schedule/config.rs deleted file mode 100644 index a8dbfc8..0000000 --- a/src/schedule/config.rs +++ /dev/null @@ -1,620 +0,0 @@ -use alloc::{boxed::Box, vec, vec::Vec}; -use variadics_please::all_tuples; - -use crate::{ - schedule::{ - auto_insert_apply_deferred::IgnoreDeferred, - condition::{BoxedCondition, SystemCondition}, - graph::{Ambiguity, Dependency, DependencyKind, GraphInfo}, - set::{InternedSystemSet, IntoSystemSet, SystemSet}, - Chain, - }, - system::{BoxedSystem, IntoSystem, ScheduleSystem, System}, -}; - -fn new_condition(condition: impl SystemCondition) -> BoxedCondition { - let condition_system = IntoSystem::into_system(condition); - assert!( - condition_system.is_send(), - "SystemCondition `{}` accesses `NonSend` resources. This is not currently supported.", - condition_system.name() - ); - - Box::new(condition_system) -} - -fn ambiguous_with(graph_info: &mut GraphInfo, set: InternedSystemSet) { - match &mut graph_info.ambiguous_with { - detection @ Ambiguity::Check => { - *detection = Ambiguity::IgnoreWithSet(vec![set]); - } - Ambiguity::IgnoreWithSet(ambiguous_with) => { - ambiguous_with.push(set); - } - Ambiguity::IgnoreAll => (), - } -} - -/// Stores data to differentiate different schedulable structs. -pub trait Schedulable { - /// Additional data used to configure independent scheduling. Stored in [`ScheduleConfig`]. - type Metadata; - /// Additional data used to configure a schedulable group. Stored in [`ScheduleConfigs`]. - type GroupMetadata; - - /// Initializes a configuration from this node. - fn into_config(self) -> ScheduleConfig - where - Self: Sized; -} - -impl Schedulable for ScheduleSystem { - type Metadata = GraphInfo; - type GroupMetadata = Chain; - - fn into_config(self) -> ScheduleConfig { - let sets = self.default_system_sets().clone(); - ScheduleConfig { - node: self, - metadata: GraphInfo { - hierarchy: sets, - ..Default::default() - }, - conditions: Vec::new(), - } - } -} - -impl Schedulable for InternedSystemSet { - type Metadata = GraphInfo; - type GroupMetadata = Chain; - - fn into_config(self) -> ScheduleConfig { - assert!( - self.system_type().is_none(), - "configuring system type sets is not allowed" - ); - - ScheduleConfig { - node: self, - metadata: GraphInfo::default(), - conditions: Vec::new(), - } - } -} - -/// Stores configuration for a single generic node (a system or a system set) -/// -/// The configuration includes the node itself, scheduling metadata -/// (hierarchy: in which sets is the node contained, -/// dependencies: before/after which other nodes should this node run) -/// and the run conditions associated with this node. -pub struct ScheduleConfig { - pub(crate) node: T, - pub(crate) metadata: T::Metadata, - pub(crate) conditions: Vec, -} - -/// Single or nested configurations for [`Schedulable`]s. -pub enum ScheduleConfigs { - /// Configuration for a single [`Schedulable`]. - ScheduleConfig(ScheduleConfig), - /// Configuration for a tuple of nested `Configs` instances. - Configs { - /// Configuration for each element of the tuple. - configs: Vec>, - /// Run conditions applied to everything in the tuple. - collective_conditions: Vec, - /// Metadata to be applied to all elements in the tuple. - metadata: T::GroupMetadata, - }, -} - -impl> ScheduleConfigs { - /// Adds a new boxed system set to the systems. - pub fn in_set_inner(&mut self, set: InternedSystemSet) { - match self { - Self::ScheduleConfig(config) => { - config.metadata.hierarchy.push(set); - } - Self::Configs { configs, .. } => { - for config in configs { - config.in_set_inner(set); - } - } - } - } - - fn before_inner(&mut self, set: InternedSystemSet) { - match self { - Self::ScheduleConfig(config) => { - config - .metadata - .dependencies - .push(Dependency::new(DependencyKind::Before, set)); - } - Self::Configs { configs, .. } => { - for config in configs { - config.before_inner(set); - } - } - } - } - - fn after_inner(&mut self, set: InternedSystemSet) { - match self { - Self::ScheduleConfig(config) => { - config - .metadata - .dependencies - .push(Dependency::new(DependencyKind::After, set)); - } - Self::Configs { configs, .. } => { - for config in configs { - config.after_inner(set); - } - } - } - } - - fn before_ignore_deferred_inner(&mut self, set: InternedSystemSet) { - match self { - Self::ScheduleConfig(config) => { - config - .metadata - .dependencies - .push(Dependency::new(DependencyKind::Before, set).add_config(IgnoreDeferred)); - } - Self::Configs { configs, .. } => { - for config in configs { - config.before_ignore_deferred_inner(set.intern()); - } - } - } - } - - fn after_ignore_deferred_inner(&mut self, set: InternedSystemSet) { - match self { - Self::ScheduleConfig(config) => { - config - .metadata - .dependencies - .push(Dependency::new(DependencyKind::After, set).add_config(IgnoreDeferred)); - } - Self::Configs { configs, .. } => { - for config in configs { - config.after_ignore_deferred_inner(set.intern()); - } - } - } - } - - fn distributive_run_if_inner(&mut self, condition: impl SystemCondition + Clone) { - match self { - Self::ScheduleConfig(config) => { - config.conditions.push(new_condition(condition)); - } - Self::Configs { configs, .. } => { - for config in configs { - config.distributive_run_if_inner(condition.clone()); - } - } - } - } - - fn ambiguous_with_inner(&mut self, set: InternedSystemSet) { - match self { - Self::ScheduleConfig(config) => { - ambiguous_with(&mut config.metadata, set); - } - Self::Configs { configs, .. } => { - for config in configs { - config.ambiguous_with_inner(set); - } - } - } - } - - fn ambiguous_with_all_inner(&mut self) { - match self { - Self::ScheduleConfig(config) => { - config.metadata.ambiguous_with = Ambiguity::IgnoreAll; - } - Self::Configs { configs, .. } => { - for config in configs { - config.ambiguous_with_all_inner(); - } - } - } - } - - /// Adds a new boxed run condition to the systems. - /// - /// This is useful if you have a run condition whose concrete type is unknown. - /// Prefer `run_if` for run conditions whose type is known at compile time. - pub fn run_if_dyn(&mut self, condition: BoxedCondition) { - match self { - Self::ScheduleConfig(config) => { - config.conditions.push(condition); - } - Self::Configs { - collective_conditions, - .. - } => { - collective_conditions.push(condition); - } - } - } - - fn chain_inner(mut self) -> Self { - match &mut self { - Self::ScheduleConfig(_) => { /* no op */ } - Self::Configs { metadata, .. } => { - metadata.set_chained(); - } - }; - self - } - - fn chain_ignore_deferred_inner(mut self) -> Self { - match &mut self { - Self::ScheduleConfig(_) => { /* no op */ } - Self::Configs { metadata, .. } => { - metadata.set_chained_with_config(IgnoreDeferred); - } - } - self - } -} - -/// Types that can convert into a [`ScheduleConfigs`]. -/// -/// This trait is implemented for "systems" (functions whose arguments all implement -/// [`SystemParam`](crate::system::SystemParam)), or tuples thereof. -/// It is a common entry point for system configurations. -/// -/// # Usage notes -/// -/// This trait should only be used as a bound for trait implementations or as an -/// argument to a function. If system configs need to be returned from a -/// function or stored somewhere, use [`ScheduleConfigs`] instead of this trait. -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::{schedule::IntoScheduleConfigs, system::ScheduleSystem}; -/// # struct AppMock; -/// # struct Update; -/// # impl AppMock { -/// # pub fn add_systems( -/// # &mut self, -/// # schedule: Update, -/// # systems: impl IntoScheduleConfigs, -/// # ) -> &mut Self { self } -/// # } -/// # let mut app = AppMock; -/// -/// fn handle_input() {} -/// -/// fn update_camera() {} -/// fn update_character() {} -/// -/// app.add_systems( -/// Update, -/// ( -/// handle_input, -/// (update_camera, update_character).after(handle_input) -/// ) -/// ); -/// ``` -#[diagnostic::on_unimplemented( - message = "`{Self}` does not describe a valid system configuration", - label = "invalid system configuration" -)] -pub trait IntoScheduleConfigs, Marker>: - Sized -{ - /// Convert into a [`ScheduleConfigs`]. - fn into_configs(self) -> ScheduleConfigs; - - /// Add these systems to the provided `set`. - #[track_caller] - fn in_set(self, set: impl SystemSet) -> ScheduleConfigs { - self.into_configs().in_set(set) - } - - /// Runs before all systems in `set`. If `self` has any systems that produce [`Commands`](crate::system::Commands) - /// or other [`Deferred`](crate::system::Deferred) operations, all systems in `set` will see their effect. - /// - /// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like - /// this isn't desired, use [`before_ignore_deferred`](Self::before_ignore_deferred) instead. - /// - /// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule. - /// Please check the [caveats section of `.after`](Self::after) for details. - fn before(self, set: impl IntoSystemSet) -> ScheduleConfigs { - self.into_configs().before(set) - } - - /// Run after all systems in `set`. If `set` has any systems that produce [`Commands`](crate::system::Commands) - /// or other [`Deferred`](crate::system::Deferred) operations, all systems in `self` will see their effect. - /// - /// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like - /// this isn't desired, use [`after_ignore_deferred`](Self::after_ignore_deferred) instead. - /// - /// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule. - /// - /// # Caveats - /// - /// If you configure two [`System`]s like `(GameSystem::A).after(GameSystem::B)` or `(GameSystem::A).before(GameSystem::B)`, the `GameSystem::B` will not be automatically scheduled. - /// - /// This means that the system `GameSystem::A` and the system or systems in `GameSystem::B` will run independently of each other if `GameSystem::B` was never explicitly scheduled with [`configure_sets`] - /// If that is the case, `.after`/`.before` will not provide the desired behavior - /// and the systems can run in parallel or in any order determined by the scheduler. - /// Only use `after(GameSystem::B)` and `before(GameSystem::B)` when you know that `B` has already been scheduled for you, - /// e.g. when it was provided by Bevy or a third-party dependency, - /// or you manually scheduled it somewhere else in your app. - /// - /// Another caveat is that if `GameSystem::B` is placed in a different schedule than `GameSystem::A`, - /// any ordering calls between them—whether using `.before`, `.after`, or `.chain`—will be silently ignored. - /// - /// [`configure_sets`]: https://docs.rs/bevy/latest/bevy/app/struct.App.html#method.configure_sets - fn after(self, set: impl IntoSystemSet) -> ScheduleConfigs { - self.into_configs().after(set) - } - - /// Run before all systems in `set`. - /// - /// Unlike [`before`](Self::before), this will not cause the systems in - /// `set` to wait for the deferred effects of `self` to be applied. - fn before_ignore_deferred(self, set: impl IntoSystemSet) -> ScheduleConfigs { - self.into_configs().before_ignore_deferred(set) - } - - /// Run after all systems in `set`. - /// - /// Unlike [`after`](Self::after), this will not wait for the deferred - /// effects of systems in `set` to be applied. - fn after_ignore_deferred(self, set: impl IntoSystemSet) -> ScheduleConfigs { - self.into_configs().after_ignore_deferred(set) - } - - /// Add a run condition to each contained system. - /// - /// Each system will receive its own clone of the [`SystemCondition`] and will only run - /// if the `SystemCondition` is true. - /// - /// Each individual condition will be evaluated at most once (per schedule run), - /// right before the corresponding system prepares to run. - /// - /// This is equivalent to calling [`run_if`](IntoScheduleConfigs::run_if) on each individual - /// system, as shown below: - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # let mut schedule = Schedule::default(); - /// # fn a() {} - /// # fn b() {} - /// # fn condition() -> bool { true } - /// schedule.add_systems((a, b).distributive_run_if(condition)); - /// schedule.add_systems((a.run_if(condition), b.run_if(condition))); - /// ``` - /// - /// # Note - /// - /// Because the conditions are evaluated separately for each system, there is no guarantee - /// that all evaluations in a single schedule run will yield the same result. If another - /// system is run inbetween two evaluations it could cause the result of the condition to change. - /// - /// Use [`run_if`](ScheduleConfigs::run_if) on a [`SystemSet`] if you want to make sure - /// that either all or none of the systems are run, or you don't want to evaluate the run - /// condition for each contained system separately. - fn distributive_run_if( - self, - condition: impl SystemCondition + Clone, - ) -> ScheduleConfigs { - self.into_configs().distributive_run_if(condition) - } - - /// Run the systems only if the [`SystemCondition`] is `true`. - /// - /// The `SystemCondition` will be evaluated at most once (per schedule run), - /// the first time a system in this set prepares to run. - /// - /// If this set contains more than one system, calling `run_if` is equivalent to adding each - /// system to a common set and configuring the run condition on that set, as shown below: - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # let mut schedule = Schedule::default(); - /// # fn a() {} - /// # fn b() {} - /// # fn condition() -> bool { true } - /// # #[derive(SystemSet, Debug, Eq, PartialEq, Hash, Clone, Copy)] - /// # struct C; - /// schedule.add_systems((a, b).run_if(condition)); - /// schedule.add_systems((a, b).in_set(C)).configure_sets(C.run_if(condition)); - /// ``` - /// - /// # Note - /// - /// Because the condition will only be evaluated once, there is no guarantee that the condition - /// is upheld after the first system has run. You need to make sure that no other systems that - /// could invalidate the condition are scheduled inbetween the first and last run system. - /// - /// Use [`distributive_run_if`](IntoScheduleConfigs::distributive_run_if) if you want the - /// condition to be evaluated for each individual system, right before one is run. - fn run_if(self, condition: impl SystemCondition) -> ScheduleConfigs { - self.into_configs().run_if(condition) - } - - /// Suppress warnings and errors that would result from these systems having ambiguities - /// (conflicting access but indeterminate order) with systems in `set`. - fn ambiguous_with(self, set: impl IntoSystemSet) -> ScheduleConfigs { - self.into_configs().ambiguous_with(set) - } - - /// Suppress warnings and errors that would result from these systems having ambiguities - /// (conflicting access but indeterminate order) with any other system. - fn ambiguous_with_all(self) -> ScheduleConfigs { - self.into_configs().ambiguous_with_all() - } - - /// Treat this collection as a sequence of systems. - /// - /// Ordering constraints will be applied between the successive elements. - /// - /// If the preceding node on an edge has deferred parameters, an [`ApplyDeferred`](crate::schedule::ApplyDeferred) - /// will be inserted on the edge. If this behavior is not desired consider using - /// [`chain_ignore_deferred`](Self::chain_ignore_deferred) instead. - fn chain(self) -> ScheduleConfigs { - self.into_configs().chain() - } - - /// Treat this collection as a sequence of systems. - /// - /// Ordering constraints will be applied between the successive elements. - /// - /// Unlike [`chain`](Self::chain) this will **not** add [`ApplyDeferred`](crate::schedule::ApplyDeferred) on the edges. - fn chain_ignore_deferred(self) -> ScheduleConfigs { - self.into_configs().chain_ignore_deferred() - } -} - -impl> IntoScheduleConfigs - for ScheduleConfigs -{ - fn into_configs(self) -> Self { - self - } - - #[track_caller] - fn in_set(mut self, set: impl SystemSet) -> Self { - assert!( - set.system_type().is_none(), - "adding arbitrary systems to a system type set is not allowed" - ); - - self.in_set_inner(set.intern()); - - self - } - - fn before(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - self.before_inner(set.intern()); - self - } - - fn after(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - self.after_inner(set.intern()); - self - } - - fn before_ignore_deferred(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - self.before_ignore_deferred_inner(set.intern()); - self - } - - fn after_ignore_deferred(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - self.after_ignore_deferred_inner(set.intern()); - self - } - - fn distributive_run_if( - mut self, - condition: impl SystemCondition + Clone, - ) -> ScheduleConfigs { - self.distributive_run_if_inner(condition); - self - } - - fn run_if(mut self, condition: impl SystemCondition) -> ScheduleConfigs { - self.run_if_dyn(new_condition(condition)); - self - } - - fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - self.ambiguous_with_inner(set.intern()); - self - } - - fn ambiguous_with_all(mut self) -> Self { - self.ambiguous_with_all_inner(); - self - } - - fn chain(self) -> Self { - self.chain_inner() - } - - fn chain_ignore_deferred(self) -> Self { - self.chain_ignore_deferred_inner() - } -} - -impl IntoScheduleConfigs for F -where - F: IntoSystem<(), (), Marker>, -{ - fn into_configs(self) -> ScheduleConfigs { - let boxed_system = Box::new(IntoSystem::into_system(self)); - ScheduleConfigs::ScheduleConfig(ScheduleSystem::into_config(boxed_system)) - } -} - -impl IntoScheduleConfigs for BoxedSystem<(), ()> { - fn into_configs(self) -> ScheduleConfigs { - ScheduleConfigs::ScheduleConfig(ScheduleSystem::into_config(self)) - } -} - -impl IntoScheduleConfigs for S { - fn into_configs(self) -> ScheduleConfigs { - ScheduleConfigs::ScheduleConfig(InternedSystemSet::into_config(self.intern())) - } -} - -#[doc(hidden)] -pub struct ScheduleConfigTupleMarker; - -macro_rules! impl_node_type_collection { - ($(#[$meta:meta])* $(($param: ident, $sys: ident)),*) => { - $(#[$meta])* - impl<$($param, $sys),*, T: Schedulable> IntoScheduleConfigs for ($($sys,)*) - where - $($sys: IntoScheduleConfigs),* - { - #[expect( - clippy::allow_attributes, - reason = "We are inside a macro, and as such, `non_snake_case` is not guaranteed to apply." - )] - #[allow( - non_snake_case, - reason = "Variable names are provided by the macro caller, not by us." - )] - fn into_configs(self) -> ScheduleConfigs { - let ($($sys,)*) = self; - ScheduleConfigs::Configs { - metadata: Default::default(), - configs: vec![$($sys.into_configs(),)*], - collective_conditions: Vec::new(), - } - } - } - } -} - -all_tuples!( - #[doc(fake_variadic)] - impl_node_type_collection, - 1, - 20, - P, - S -); diff --git a/src/schedule/executor/mod.rs b/src/schedule/executor/mod.rs deleted file mode 100644 index 12030c2..0000000 --- a/src/schedule/executor/mod.rs +++ /dev/null @@ -1,581 +0,0 @@ -#[cfg(feature = "std")] -mod multi_threaded; -mod simple; -mod single_threaded; - -use alloc::{vec, vec::Vec}; -use bevy_utils::prelude::DebugName; -use core::any::TypeId; - -#[expect(deprecated, reason = "We still need to support this.")] -pub use self::{simple::SimpleExecutor, single_threaded::SingleThreadedExecutor}; - -#[cfg(feature = "std")] -pub use self::multi_threaded::{MainThreadExecutor, MultiThreadedExecutor}; - -use fixedbitset::FixedBitSet; - -use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, - error::{BevyError, ErrorContext, Result}, - prelude::{IntoSystemSet, SystemSet}, - query::FilteredAccessSet, - schedule::{ - ConditionWithAccess, InternedSystemSet, SystemKey, SystemSetKey, SystemTypeSet, - SystemWithAccess, - }, - system::{ - RunSystemError, ScheduleSystem, System, SystemIn, SystemParamValidationError, - SystemStateFlags, - }, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, -}; - -/// Types that can run a [`SystemSchedule`] on a [`World`]. -pub(super) trait SystemExecutor: Send + Sync { - fn kind(&self) -> ExecutorKind; - fn init(&mut self, schedule: &SystemSchedule); - fn run( - &mut self, - schedule: &mut SystemSchedule, - world: &mut World, - skip_systems: Option<&FixedBitSet>, - error_handler: fn(BevyError, ErrorContext), - ); - fn set_apply_final_deferred(&mut self, value: bool); -} - -/// Specifies how a [`Schedule`](super::Schedule) will be run. -/// -/// The default depends on the target platform: -/// - [`SingleThreaded`](ExecutorKind::SingleThreaded) on Wasm. -/// - [`MultiThreaded`](ExecutorKind::MultiThreaded) everywhere else. -#[derive(PartialEq, Eq, Default, Debug, Copy, Clone)] -pub enum ExecutorKind { - /// Runs the schedule using a single thread. - /// - /// Useful if you're dealing with a single-threaded environment, saving your threads for - /// other things, or just trying minimize overhead. - #[cfg_attr(any(target_arch = "wasm32", not(feature = "multi_threaded")), default)] - SingleThreaded, - /// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_deferred`](crate::system::System::apply_deferred) - /// immediately after running each system. - #[deprecated( - since = "0.17.0", - note = "Use SingleThreaded instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation." - )] - Simple, - /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. - #[cfg(feature = "std")] - #[cfg_attr(all(not(target_arch = "wasm32"), feature = "multi_threaded"), default)] - MultiThreaded, -} - -/// Holds systems and conditions of a [`Schedule`](super::Schedule) sorted in topological order -/// (along with dependency information for `multi_threaded` execution). -/// -/// Since the arrays are sorted in the same order, elements are referenced by their index. -/// [`FixedBitSet`] is used as a smaller, more efficient substitute of `HashSet`. -#[derive(Default)] -pub struct SystemSchedule { - /// List of system node ids. - pub(super) system_ids: Vec, - /// Indexed by system node id. - pub(super) systems: Vec, - /// Indexed by system node id. - pub(super) system_conditions: Vec>, - /// Indexed by system node id. - /// Number of systems that the system immediately depends on. - #[cfg_attr( - not(feature = "std"), - expect(dead_code, reason = "currently only used with the std feature") - )] - pub(super) system_dependencies: Vec, - /// Indexed by system node id. - /// List of systems that immediately depend on the system. - #[cfg_attr( - not(feature = "std"), - expect(dead_code, reason = "currently only used with the std feature") - )] - pub(super) system_dependents: Vec>, - /// Indexed by system node id. - /// List of sets containing the system that have conditions - pub(super) sets_with_conditions_of_systems: Vec, - /// List of system set node ids. - pub(super) set_ids: Vec, - /// Indexed by system set node id. - pub(super) set_conditions: Vec>, - /// Indexed by system set node id. - /// List of systems that are in sets that have conditions. - /// - /// If a set doesn't run because of its conditions, this is used to skip all systems in it. - pub(super) systems_in_sets_with_conditions: Vec, -} - -impl SystemSchedule { - /// Creates an empty [`SystemSchedule`]. - pub const fn new() -> Self { - Self { - systems: Vec::new(), - system_conditions: Vec::new(), - set_conditions: Vec::new(), - system_ids: Vec::new(), - set_ids: Vec::new(), - system_dependencies: Vec::new(), - system_dependents: Vec::new(), - sets_with_conditions_of_systems: Vec::new(), - systems_in_sets_with_conditions: Vec::new(), - } - } -} - -/// A special [`System`] that instructs the executor to call -/// [`System::apply_deferred`] on the systems that have run but not applied -/// their [`Deferred`] system parameters (like [`Commands`]) or other system buffers. -/// -/// ## Scheduling -/// -/// `ApplyDeferred` systems are scheduled *by default* -/// - later in the same schedule run (for example, if a system with `Commands` param -/// is scheduled in `Update`, all the changes will be visible in `PostUpdate`) -/// - between systems with dependencies if the dependency [has deferred buffers] -/// (if system `bar` directly or indirectly depends on `foo`, and `foo` uses -/// `Commands` param, changes to the world in `foo` will be visible in `bar`) -/// -/// ## Notes -/// - This system (currently) does nothing if it's called manually or wrapped -/// inside a [`PipeSystem`]. -/// - Modifying a [`Schedule`] may change the order buffers are applied. -/// -/// [`System::apply_deferred`]: crate::system::System::apply_deferred -/// [`Deferred`]: crate::system::Deferred -/// [`Commands`]: crate::prelude::Commands -/// [has deferred buffers]: crate::system::System::has_deferred -/// [`PipeSystem`]: crate::system::PipeSystem -/// [`Schedule`]: super::Schedule -#[doc(alias = "apply_system_buffers")] -pub struct ApplyDeferred; - -/// Returns `true` if the [`System`] is an instance of [`ApplyDeferred`]. -pub(super) fn is_apply_deferred(system: &ScheduleSystem) -> bool { - system.type_id() == TypeId::of::() -} - -impl System for ApplyDeferred { - type In = (); - type Out = (); - - fn name(&self) -> DebugName { - DebugName::borrowed("bevy_ecs::apply_deferred") - } - - fn flags(&self) -> SystemStateFlags { - // non-send , exclusive , no deferred - SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE - } - - unsafe fn run_unsafe( - &mut self, - _input: SystemIn<'_, Self>, - _world: UnsafeWorldCell, - ) -> Result { - // This system does nothing on its own. The executor will apply deferred - // commands from other systems instead of running this system. - Ok(()) - } - - #[cfg(feature = "hotpatching")] - #[inline] - fn refresh_hotpatch(&mut self) {} - - fn run( - &mut self, - _input: SystemIn<'_, Self>, - _world: &mut World, - ) -> Result { - // This system does nothing on its own. The executor will apply deferred - // commands from other systems instead of running this system. - Ok(()) - } - - fn apply_deferred(&mut self, _world: &mut World) {} - - fn queue_deferred(&mut self, _world: DeferredWorld) {} - - unsafe fn validate_param_unsafe( - &mut self, - _world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // This system is always valid to run because it doesn't do anything, - // and only used as a marker for the executor. - Ok(()) - } - - fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet { - FilteredAccessSet::new() - } - - fn check_change_tick(&mut self, _check: CheckChangeTicks) {} - - fn default_system_sets(&self) -> Vec { - vec![SystemTypeSet::::new().intern()] - } - - fn get_last_run(&self) -> Tick { - // This system is never run, so it has no last run tick. - Tick::MAX - } - - fn set_last_run(&mut self, _last_run: Tick) {} -} - -impl IntoSystemSet<()> for ApplyDeferred { - type Set = SystemTypeSet; - - fn into_system_set(self) -> Self::Set { - SystemTypeSet::::new() - } -} - -/// These functions hide the bottom of the callstack from `RUST_BACKTRACE=1` (assuming the default panic handler is used). -/// -/// The full callstack will still be visible with `RUST_BACKTRACE=full`. -/// They are specialized for `System::run` & co instead of being generic over closures because this avoids an -/// extra frame in the backtrace. -/// -/// This is reliant on undocumented behavior in Rust's default panic handler, which checks the call stack for symbols -/// containing the string `__rust_begin_short_backtrace` in their mangled name. -mod __rust_begin_short_backtrace { - use core::hint::black_box; - - #[cfg(feature = "std")] - use crate::world::unsafe_world_cell::UnsafeWorldCell; - use crate::{ - error::Result, - system::{ReadOnlySystem, RunSystemError, ScheduleSystem}, - world::World, - }; - - /// # Safety - /// See `System::run_unsafe`. - // This is only used by `MultiThreadedExecutor`, and would be dead code without `std`. - #[cfg(feature = "std")] - #[inline(never)] - pub(super) unsafe fn run_unsafe( - system: &mut ScheduleSystem, - world: UnsafeWorldCell, - ) -> Result<(), RunSystemError> { - let result = system.run_unsafe((), world); - // Call `black_box` to prevent this frame from being tail-call optimized away - black_box(()); - result - } - - /// # Safety - /// See `ReadOnlySystem::run_unsafe`. - // This is only used by `MultiThreadedExecutor`, and would be dead code without `std`. - #[cfg(feature = "std")] - #[inline(never)] - pub(super) unsafe fn readonly_run_unsafe( - system: &mut dyn ReadOnlySystem, - world: UnsafeWorldCell, - ) -> Result { - // Call `black_box` to prevent this frame from being tail-call optimized away - black_box(system.run_unsafe((), world)) - } - - #[inline(never)] - pub(super) fn run( - system: &mut ScheduleSystem, - world: &mut World, - ) -> Result<(), RunSystemError> { - let result = system.run((), world); - // Call `black_box` to prevent this frame from being tail-call optimized away - black_box(()); - result - } - - #[inline(never)] - pub(super) fn run_without_applying_deferred( - system: &mut ScheduleSystem, - world: &mut World, - ) -> Result<(), RunSystemError> { - let result = system.run_without_applying_deferred((), world); - // Call `black_box` to prevent this frame from being tail-call optimized away - black_box(()); - result - } - - #[inline(never)] - pub(super) fn readonly_run( - system: &mut dyn ReadOnlySystem, - world: &mut World, - ) -> Result { - // Call `black_box` to prevent this frame from being tail-call optimized away - black_box(system.run((), world)) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - prelude::{Component, In, IntoSystem, Resource, Schedule}, - schedule::ExecutorKind, - system::{Populated, Res, ResMut, Single}, - world::World, - }; - - #[derive(Component)] - struct TestComponent; - - const EXECUTORS: [ExecutorKind; 3] = [ - #[expect(deprecated, reason = "We still need to test this.")] - ExecutorKind::Simple, - ExecutorKind::SingleThreaded, - ExecutorKind::MultiThreaded, - ]; - - #[derive(Resource, Default)] - struct TestState { - populated_ran: bool, - single_ran: bool, - } - - #[derive(Resource, Default)] - struct Counter(u8); - - fn set_single_state(mut _single: Single<&TestComponent>, mut state: ResMut) { - state.single_ran = true; - } - - fn set_populated_state( - mut _populated: Populated<&TestComponent>, - mut state: ResMut, - ) { - state.populated_ran = true; - } - - #[test] - #[expect(clippy::print_stdout, reason = "std and println are allowed in tests")] - fn single_and_populated_skipped_and_run() { - for executor in EXECUTORS { - std::println!("Testing executor: {executor:?}"); - - let mut world = World::new(); - world.init_resource::(); - - let mut schedule = Schedule::default(); - schedule.set_executor_kind(executor); - schedule.add_systems((set_single_state, set_populated_state)); - schedule.run(&mut world); - - let state = world.get_resource::().unwrap(); - assert!(!state.single_ran); - assert!(!state.populated_ran); - - world.spawn(TestComponent); - - schedule.run(&mut world); - let state = world.get_resource::().unwrap(); - assert!(state.single_ran); - assert!(state.populated_ran); - } - } - - fn look_for_missing_resource(_res: Res) {} - - #[test] - #[should_panic] - fn missing_resource_panics_simple() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - #[expect(deprecated, reason = "We still need to test this.")] - schedule.set_executor_kind(ExecutorKind::Simple); - schedule.add_systems(look_for_missing_resource); - schedule.run(&mut world); - } - - #[test] - #[should_panic] - fn missing_resource_panics_single_threaded() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.set_executor_kind(ExecutorKind::SingleThreaded); - schedule.add_systems(look_for_missing_resource); - schedule.run(&mut world); - } - - #[test] - #[should_panic] - fn missing_resource_panics_multi_threaded() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.set_executor_kind(ExecutorKind::MultiThreaded); - schedule.add_systems(look_for_missing_resource); - schedule.run(&mut world); - } - - #[test] - fn piped_systems_first_system_skipped() { - // This system should be skipped when run due to no matching entity - fn pipe_out(_single: Single<&TestComponent>) -> u8 { - 42 - } - - fn pipe_in(_input: In, mut counter: ResMut) { - counter.0 += 1; - } - - let mut world = World::new(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - schedule.add_systems(pipe_out.pipe(pipe_in)); - schedule.run(&mut world); - - let counter = world.resource::(); - assert_eq!(counter.0, 0); - } - - #[test] - fn piped_system_second_system_skipped() { - // This system will be run before the second system is validated - fn pipe_out(mut counter: ResMut) -> u8 { - counter.0 += 1; - 42 - } - - // This system should be skipped when run due to no matching entity - fn pipe_in(_input: In, _single: Single<&TestComponent>, mut counter: ResMut) { - counter.0 += 1; - } - - let mut world = World::new(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - schedule.add_systems(pipe_out.pipe(pipe_in)); - schedule.run(&mut world); - let counter = world.resource::(); - assert_eq!(counter.0, 1); - } - - #[test] - #[should_panic] - fn piped_system_first_system_panics() { - // This system should panic when run because the resource is missing - fn pipe_out(_res: Res) -> u8 { - 42 - } - - fn pipe_in(_input: In) {} - - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.add_systems(pipe_out.pipe(pipe_in)); - schedule.run(&mut world); - } - - #[test] - #[should_panic] - fn piped_system_second_system_panics() { - fn pipe_out() -> u8 { - 42 - } - - // This system should panic when run because the resource is missing - fn pipe_in(_input: In, _res: Res) {} - - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.add_systems(pipe_out.pipe(pipe_in)); - schedule.run(&mut world); - } - - // This test runs without panicking because we've - // decided to use early-out behavior for piped systems - #[test] - fn piped_system_skip_and_panic() { - // This system should be skipped when run due to no matching entity - fn pipe_out(_single: Single<&TestComponent>) -> u8 { - 42 - } - - // This system should panic when run because the resource is missing - fn pipe_in(_input: In, _res: Res) {} - - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.add_systems(pipe_out.pipe(pipe_in)); - schedule.run(&mut world); - } - - #[test] - #[should_panic] - fn piped_system_panic_and_skip() { - // This system should panic when run because the resource is missing - - fn pipe_out(_res: Res) -> u8 { - 42 - } - - // This system should be skipped when run due to no matching entity - fn pipe_in(_input: In, _single: Single<&TestComponent>) {} - - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.add_systems(pipe_out.pipe(pipe_in)); - schedule.run(&mut world); - } - - #[test] - #[should_panic] - fn piped_system_panic_and_panic() { - // This system should panic when run because the resource is missing - - fn pipe_out(_res: Res) -> u8 { - 42 - } - - // This system should panic when run because the resource is missing - fn pipe_in(_input: In, _res: Res) {} - - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.add_systems(pipe_out.pipe(pipe_in)); - schedule.run(&mut world); - } - - #[test] - fn piped_system_skip_and_skip() { - // This system should be skipped when run due to no matching entity - - fn pipe_out(_single: Single<&TestComponent>, mut counter: ResMut) -> u8 { - counter.0 += 1; - 42 - } - - // This system should be skipped when run due to no matching entity - fn pipe_in(_input: In, _single: Single<&TestComponent>, mut counter: ResMut) { - counter.0 += 1; - } - - let mut world = World::new(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - schedule.add_systems(pipe_out.pipe(pipe_in)); - schedule.run(&mut world); - - let counter = world.resource::(); - assert_eq!(counter.0, 0); - } -} diff --git a/src/schedule/executor/multi_threaded.rs b/src/schedule/executor/multi_threaded.rs deleted file mode 100644 index 006faa8..0000000 --- a/src/schedule/executor/multi_threaded.rs +++ /dev/null @@ -1,923 +0,0 @@ -use alloc::{boxed::Box, vec::Vec}; -use bevy_platform::cell::SyncUnsafeCell; -use bevy_platform::sync::Arc; -use bevy_tasks::{ComputeTaskPool, Scope, TaskPool, ThreadExecutor}; -use concurrent_queue::ConcurrentQueue; -use core::{any::Any, panic::AssertUnwindSafe}; -use fixedbitset::FixedBitSet; -#[cfg(feature = "std")] -use std::eprintln; -use std::sync::{Mutex, MutexGuard}; - -#[cfg(feature = "trace")] -use tracing::{info_span, Span}; - -use crate::{ - error::{ErrorContext, ErrorHandler, Result}, - prelude::Resource, - schedule::{ - is_apply_deferred, ConditionWithAccess, ExecutorKind, SystemExecutor, SystemSchedule, - SystemWithAccess, - }, - system::{RunSystemError, ScheduleSystem}, - world::{unsafe_world_cell::UnsafeWorldCell, World}, -}; -#[cfg(feature = "hotpatching")] -use crate::{event::Events, HotPatched}; - -use super::__rust_begin_short_backtrace; - -/// Borrowed data used by the [`MultiThreadedExecutor`]. -struct Environment<'env, 'sys> { - executor: &'env MultiThreadedExecutor, - systems: &'sys [SyncUnsafeCell], - conditions: SyncUnsafeCell>, - world_cell: UnsafeWorldCell<'env>, -} - -struct Conditions<'a> { - system_conditions: &'a mut [Vec], - set_conditions: &'a mut [Vec], - sets_with_conditions_of_systems: &'a [FixedBitSet], - systems_in_sets_with_conditions: &'a [FixedBitSet], -} - -impl<'env, 'sys> Environment<'env, 'sys> { - fn new( - executor: &'env MultiThreadedExecutor, - schedule: &'sys mut SystemSchedule, - world: &'env mut World, - ) -> Self { - Environment { - executor, - systems: SyncUnsafeCell::from_mut(schedule.systems.as_mut_slice()).as_slice_of_cells(), - conditions: SyncUnsafeCell::new(Conditions { - system_conditions: &mut schedule.system_conditions, - set_conditions: &mut schedule.set_conditions, - sets_with_conditions_of_systems: &schedule.sets_with_conditions_of_systems, - systems_in_sets_with_conditions: &schedule.systems_in_sets_with_conditions, - }), - world_cell: world.as_unsafe_world_cell(), - } - } -} - -/// Per-system data used by the [`MultiThreadedExecutor`]. -// Copied here because it can't be read from the system when it's running. -struct SystemTaskMetadata { - /// The set of systems whose `component_access_set()` conflicts with this one. - conflicting_systems: FixedBitSet, - /// The set of systems whose `component_access_set()` conflicts with this system's conditions. - /// Note that this is separate from `conflicting_systems` to handle the case where - /// a system is skipped by an earlier system set condition or system stepping, - /// and needs access to run its conditions but not for itself. - condition_conflicting_systems: FixedBitSet, - /// Indices of the systems that directly depend on the system. - dependents: Vec, - /// Is `true` if the system does not access `!Send` data. - is_send: bool, - /// Is `true` if the system is exclusive. - is_exclusive: bool, -} - -/// The result of running a system that is sent across a channel. -struct SystemResult { - system_index: usize, -} - -/// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. -pub struct MultiThreadedExecutor { - /// The running state, protected by a mutex so that a reference to the executor can be shared across tasks. - state: Mutex, - /// Queue of system completion events. - system_completion: ConcurrentQueue, - /// Setting when true applies deferred system buffers after all systems have run - apply_final_deferred: bool, - /// When set, tells the executor that a thread has panicked. - panic_payload: Mutex>>, - starting_systems: FixedBitSet, - /// Cached tracing span - #[cfg(feature = "trace")] - executor_span: Span, -} - -/// The state of the executor while running. -pub struct ExecutorState { - /// Metadata for scheduling and running system tasks. - system_task_metadata: Vec, - /// The set of systems whose `component_access_set()` conflicts with this system set's conditions. - set_condition_conflicting_systems: Vec, - /// Returns `true` if a system with non-`Send` access is running. - local_thread_running: bool, - /// Returns `true` if an exclusive system is running. - exclusive_running: bool, - /// The number of systems that are running. - num_running_systems: usize, - /// The number of dependencies each system has that have not completed. - num_dependencies_remaining: Vec, - /// System sets whose conditions have been evaluated. - evaluated_sets: FixedBitSet, - /// Systems that have no remaining dependencies and are waiting to run. - ready_systems: FixedBitSet, - /// copy of `ready_systems` - ready_systems_copy: FixedBitSet, - /// Systems that are running. - running_systems: FixedBitSet, - /// Systems that got skipped. - skipped_systems: FixedBitSet, - /// Systems whose conditions have been evaluated and were run or skipped. - completed_systems: FixedBitSet, - /// Systems that have run but have not had their buffers applied. - unapplied_systems: FixedBitSet, -} - -/// References to data required by the executor. -/// This is copied to each system task so that can invoke the executor when they complete. -// These all need to outlive 'scope in order to be sent to new tasks, -// and keeping them all in a struct means we can use lifetime elision. -#[derive(Copy, Clone)] -struct Context<'scope, 'env, 'sys> { - environment: &'env Environment<'env, 'sys>, - scope: &'scope Scope<'scope, 'env, ()>, - error_handler: ErrorHandler, -} - -impl Default for MultiThreadedExecutor { - fn default() -> Self { - Self::new() - } -} - -impl SystemExecutor for MultiThreadedExecutor { - fn kind(&self) -> ExecutorKind { - ExecutorKind::MultiThreaded - } - - fn init(&mut self, schedule: &SystemSchedule) { - let state = self.state.get_mut().unwrap(); - // pre-allocate space - let sys_count = schedule.system_ids.len(); - let set_count = schedule.set_ids.len(); - - self.system_completion = ConcurrentQueue::bounded(sys_count.max(1)); - self.starting_systems = FixedBitSet::with_capacity(sys_count); - state.evaluated_sets = FixedBitSet::with_capacity(set_count); - state.ready_systems = FixedBitSet::with_capacity(sys_count); - state.ready_systems_copy = FixedBitSet::with_capacity(sys_count); - state.running_systems = FixedBitSet::with_capacity(sys_count); - state.completed_systems = FixedBitSet::with_capacity(sys_count); - state.skipped_systems = FixedBitSet::with_capacity(sys_count); - state.unapplied_systems = FixedBitSet::with_capacity(sys_count); - - state.system_task_metadata = Vec::with_capacity(sys_count); - for index in 0..sys_count { - state.system_task_metadata.push(SystemTaskMetadata { - conflicting_systems: FixedBitSet::with_capacity(sys_count), - condition_conflicting_systems: FixedBitSet::with_capacity(sys_count), - dependents: schedule.system_dependents[index].clone(), - is_send: schedule.systems[index].system.is_send(), - is_exclusive: schedule.systems[index].system.is_exclusive(), - }); - if schedule.system_dependencies[index] == 0 { - self.starting_systems.insert(index); - } - } - - { - #[cfg(feature = "trace")] - let _span = info_span!("calculate conflicting systems").entered(); - for index1 in 0..sys_count { - let system1 = &schedule.systems[index1]; - for index2 in 0..index1 { - let system2 = &schedule.systems[index2]; - if !system2.access.is_compatible(&system1.access) { - state.system_task_metadata[index1] - .conflicting_systems - .insert(index2); - state.system_task_metadata[index2] - .conflicting_systems - .insert(index1); - } - } - - for index2 in 0..sys_count { - let system2 = &schedule.systems[index2]; - if schedule.system_conditions[index1] - .iter() - .any(|condition| !system2.access.is_compatible(&condition.access)) - { - state.system_task_metadata[index1] - .condition_conflicting_systems - .insert(index2); - } - } - } - - state.set_condition_conflicting_systems.clear(); - state.set_condition_conflicting_systems.reserve(set_count); - for set_idx in 0..set_count { - let mut conflicting_systems = FixedBitSet::with_capacity(sys_count); - for sys_index in 0..sys_count { - let system = &schedule.systems[sys_index]; - if schedule.set_conditions[set_idx] - .iter() - .any(|condition| !system.access.is_compatible(&condition.access)) - { - conflicting_systems.insert(sys_index); - } - } - state - .set_condition_conflicting_systems - .push(conflicting_systems); - } - } - - state.num_dependencies_remaining = Vec::with_capacity(sys_count); - } - - fn run( - &mut self, - schedule: &mut SystemSchedule, - world: &mut World, - _skip_systems: Option<&FixedBitSet>, - error_handler: ErrorHandler, - ) { - let state = self.state.get_mut().unwrap(); - // reset counts - if schedule.systems.is_empty() { - return; - } - state.num_running_systems = 0; - state - .num_dependencies_remaining - .clone_from(&schedule.system_dependencies); - state.ready_systems.clone_from(&self.starting_systems); - - // If stepping is enabled, make sure we skip those systems that should - // not be run. - #[cfg(feature = "bevy_debug_stepping")] - if let Some(skipped_systems) = _skip_systems { - debug_assert_eq!(skipped_systems.len(), state.completed_systems.len()); - // mark skipped systems as completed - state.completed_systems |= skipped_systems; - - // signal the dependencies for each of the skipped systems, as - // though they had run - for system_index in skipped_systems.ones() { - state.signal_dependents(system_index); - state.ready_systems.remove(system_index); - } - } - - let thread_executor = world - .get_resource::() - .map(|e| e.0.clone()); - let thread_executor = thread_executor.as_deref(); - - let environment = &Environment::new(self, schedule, world); - - ComputeTaskPool::get_or_init(TaskPool::default).scope_with_executor( - false, - thread_executor, - |scope| { - let context = Context { - environment, - scope, - error_handler, - }; - - // The first tick won't need to process finished systems, but we still need to run the loop in - // tick_executor() in case a system completes while the first tick still holds the mutex. - context.tick_executor(); - }, - ); - - // End the borrows of self and world in environment by copying out the reference to systems. - let systems = environment.systems; - - let state = self.state.get_mut().unwrap(); - if self.apply_final_deferred { - // Do one final apply buffers after all systems have completed - // Commands should be applied while on the scope's thread, not the executor's thread - let res = apply_deferred(&state.unapplied_systems, systems, world); - if let Err(payload) = res { - let panic_payload = self.panic_payload.get_mut().unwrap(); - *panic_payload = Some(payload); - } - state.unapplied_systems.clear(); - } - - // check to see if there was a panic - let payload = self.panic_payload.get_mut().unwrap(); - if let Some(payload) = payload.take() { - std::panic::resume_unwind(payload); - } - - debug_assert!(state.ready_systems.is_clear()); - debug_assert!(state.running_systems.is_clear()); - state.evaluated_sets.clear(); - state.skipped_systems.clear(); - state.completed_systems.clear(); - } - - fn set_apply_final_deferred(&mut self, value: bool) { - self.apply_final_deferred = value; - } -} - -impl<'scope, 'env: 'scope, 'sys> Context<'scope, 'env, 'sys> { - fn system_completed( - &self, - system_index: usize, - res: Result<(), Box>, - system: &ScheduleSystem, - ) { - // tell the executor that the system finished - self.environment - .executor - .system_completion - .push(SystemResult { system_index }) - .unwrap_or_else(|error| unreachable!("{}", error)); - if let Err(payload) = res { - #[cfg(feature = "std")] - #[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")] - { - eprintln!("Encountered a panic in system `{}`!", system.name()); - } - // set the payload to propagate the error - { - let mut panic_payload = self.environment.executor.panic_payload.lock().unwrap(); - *panic_payload = Some(payload); - } - } - self.tick_executor(); - } - - #[expect( - clippy::mut_from_ref, - reason = "Field is only accessed here and is guarded by lock with a documented safety comment" - )] - fn try_lock<'a>(&'a self) -> Option<(&'a mut Conditions<'sys>, MutexGuard<'a, ExecutorState>)> { - let guard = self.environment.executor.state.try_lock().ok()?; - // SAFETY: This is an exclusive access as no other location fetches conditions mutably, and - // is synchronized by the lock on the executor state. - let conditions = unsafe { &mut *self.environment.conditions.get() }; - Some((conditions, guard)) - } - - fn tick_executor(&self) { - // Ensure that the executor handles any events pushed to the system_completion queue by this thread. - // If this thread acquires the lock, the executor runs after the push() and they are processed. - // If this thread does not acquire the lock, then the is_empty() check on the other thread runs - // after the lock is released, which is after try_lock() failed, which is after the push() - // on this thread, so the is_empty() check will see the new events and loop. - loop { - let Some((conditions, mut guard)) = self.try_lock() else { - return; - }; - guard.tick(self, conditions); - // Make sure we drop the guard before checking system_completion.is_empty(), or we could lose events. - drop(guard); - if self.environment.executor.system_completion.is_empty() { - return; - } - } - } -} - -impl MultiThreadedExecutor { - /// Creates a new `multi_threaded` executor for use with a [`Schedule`]. - /// - /// [`Schedule`]: crate::schedule::Schedule - pub fn new() -> Self { - Self { - state: Mutex::new(ExecutorState::new()), - system_completion: ConcurrentQueue::unbounded(), - starting_systems: FixedBitSet::new(), - apply_final_deferred: true, - panic_payload: Mutex::new(None), - #[cfg(feature = "trace")] - executor_span: info_span!("multithreaded executor"), - } - } -} - -impl ExecutorState { - fn new() -> Self { - Self { - system_task_metadata: Vec::new(), - set_condition_conflicting_systems: Vec::new(), - num_running_systems: 0, - num_dependencies_remaining: Vec::new(), - local_thread_running: false, - exclusive_running: false, - evaluated_sets: FixedBitSet::new(), - ready_systems: FixedBitSet::new(), - ready_systems_copy: FixedBitSet::new(), - running_systems: FixedBitSet::new(), - skipped_systems: FixedBitSet::new(), - completed_systems: FixedBitSet::new(), - unapplied_systems: FixedBitSet::new(), - } - } - - fn tick(&mut self, context: &Context, conditions: &mut Conditions) { - #[cfg(feature = "trace")] - let _span = context.environment.executor.executor_span.enter(); - - for result in context.environment.executor.system_completion.try_iter() { - self.finish_system_and_handle_dependents(result); - } - - // SAFETY: - // - `finish_system_and_handle_dependents` has updated the currently running systems. - // - `rebuild_active_access` locks access for all currently running systems. - unsafe { - self.spawn_system_tasks(context, conditions); - } - } - - /// # Safety - /// - Caller must ensure that `self.ready_systems` does not contain any systems that - /// have been mutably borrowed (such as the systems currently running). - /// - `world_cell` must have permission to access all world data (not counting - /// any world data that is claimed by systems currently running on this executor). - unsafe fn spawn_system_tasks(&mut self, context: &Context, conditions: &mut Conditions) { - if self.exclusive_running { - return; - } - - #[cfg(feature = "hotpatching")] - let should_update_hotpatch = !context - .environment - .world_cell - .get_resource::>() - .map(Events::is_empty) - .unwrap_or(true); - - // can't borrow since loop mutably borrows `self` - let mut ready_systems = core::mem::take(&mut self.ready_systems_copy); - - // Skipping systems may cause their dependents to become ready immediately. - // If that happens, we need to run again immediately or we may fail to spawn those dependents. - let mut check_for_new_ready_systems = true; - while check_for_new_ready_systems { - check_for_new_ready_systems = false; - - ready_systems.clone_from(&self.ready_systems); - - for system_index in ready_systems.ones() { - debug_assert!(!self.running_systems.contains(system_index)); - // SAFETY: Caller assured that these systems are not running. - // Therefore, no other reference to this system exists and there is no aliasing. - let system = - &mut unsafe { &mut *context.environment.systems[system_index].get() }.system; - - #[cfg(feature = "hotpatching")] - if should_update_hotpatch { - system.refresh_hotpatch(); - } - - if !self.can_run(system_index, conditions) { - // NOTE: exclusive systems with ambiguities are susceptible to - // being significantly displaced here (compared to single-threaded order) - // if systems after them in topological order can run - // if that becomes an issue, `break;` if exclusive system - continue; - } - - self.ready_systems.remove(system_index); - - // SAFETY: `can_run` returned true, which means that: - // - There can be no systems running whose accesses would conflict with any conditions. - if unsafe { - !self.should_run( - system_index, - system, - conditions, - context.environment.world_cell, - context.error_handler, - ) - } { - self.skip_system_and_signal_dependents(system_index); - // signal_dependents may have set more systems to ready. - check_for_new_ready_systems = true; - continue; - } - - self.running_systems.insert(system_index); - self.num_running_systems += 1; - - if self.system_task_metadata[system_index].is_exclusive { - // SAFETY: `can_run` returned true for this system, - // which means no systems are currently borrowed. - unsafe { - self.spawn_exclusive_system_task(context, system_index); - } - check_for_new_ready_systems = false; - break; - } - - // SAFETY: - // - Caller ensured no other reference to this system exists. - // - `system_task_metadata[system_index].is_exclusive` is `false`, - // so `System::is_exclusive` returned `false` when we called it. - // - `can_run` returned true, so no systems with conflicting world access are running. - unsafe { - self.spawn_system_task(context, system_index); - } - } - } - - // give back - self.ready_systems_copy = ready_systems; - } - - fn can_run(&mut self, system_index: usize, conditions: &mut Conditions) -> bool { - let system_meta = &self.system_task_metadata[system_index]; - if system_meta.is_exclusive && self.num_running_systems > 0 { - return false; - } - - if !system_meta.is_send && self.local_thread_running { - return false; - } - - // TODO: an earlier out if world's archetypes did not change - for set_idx in conditions.sets_with_conditions_of_systems[system_index] - .difference(&self.evaluated_sets) - { - if !self.set_condition_conflicting_systems[set_idx].is_disjoint(&self.running_systems) { - return false; - } - } - - if !system_meta - .condition_conflicting_systems - .is_disjoint(&self.running_systems) - { - return false; - } - - if !self.skipped_systems.contains(system_index) - && !system_meta - .conflicting_systems - .is_disjoint(&self.running_systems) - { - return false; - } - - true - } - - /// # Safety - /// * `world` must have permission to read any world data required by - /// the system's conditions: this includes conditions for the system - /// itself, and conditions for any of the system's sets. - unsafe fn should_run( - &mut self, - system_index: usize, - system: &mut ScheduleSystem, - conditions: &mut Conditions, - world: UnsafeWorldCell, - error_handler: ErrorHandler, - ) -> bool { - let mut should_run = !self.skipped_systems.contains(system_index); - - for set_idx in conditions.sets_with_conditions_of_systems[system_index].ones() { - if self.evaluated_sets.contains(set_idx) { - continue; - } - - // Evaluate the system set's conditions. - // SAFETY: - // - The caller ensures that `world` has permission to read any data - // required by the conditions. - let set_conditions_met = unsafe { - evaluate_and_fold_conditions( - &mut conditions.set_conditions[set_idx], - world, - error_handler, - ) - }; - - if !set_conditions_met { - self.skipped_systems - .union_with(&conditions.systems_in_sets_with_conditions[set_idx]); - } - - should_run &= set_conditions_met; - self.evaluated_sets.insert(set_idx); - } - - // Evaluate the system's conditions. - // SAFETY: - // - The caller ensures that `world` has permission to read any data - // required by the conditions. - let system_conditions_met = unsafe { - evaluate_and_fold_conditions( - &mut conditions.system_conditions[system_index], - world, - error_handler, - ) - }; - - if !system_conditions_met { - self.skipped_systems.insert(system_index); - } - - should_run &= system_conditions_met; - - if should_run { - // SAFETY: - // - The caller ensures that `world` has permission to read any data - // required by the system. - let valid_params = match unsafe { system.validate_param_unsafe(world) } { - Ok(()) => true, - Err(e) => { - if !e.skipped { - error_handler( - e.into(), - ErrorContext::System { - name: system.name(), - last_run: system.get_last_run(), - }, - ); - } - false - } - }; - if !valid_params { - self.skipped_systems.insert(system_index); - } - - should_run &= valid_params; - } - - should_run - } - - /// # Safety - /// - Caller must not alias systems that are running. - /// - `is_exclusive` must have returned `false` for the specified system. - /// - `world` must have permission to access the world data - /// used by the specified system. - unsafe fn spawn_system_task(&mut self, context: &Context, system_index: usize) { - // SAFETY: this system is not running, no other reference exists - let system = &mut unsafe { &mut *context.environment.systems[system_index].get() }.system; - // Move the full context object into the new future. - let context = *context; - - let system_meta = &self.system_task_metadata[system_index]; - - let task = async move { - let res = std::panic::catch_unwind(AssertUnwindSafe(|| { - // SAFETY: - // - The caller ensures that we have permission to - // access the world data used by the system. - // - `is_exclusive` returned false - unsafe { - if let Err(RunSystemError::Failed(err)) = - __rust_begin_short_backtrace::run_unsafe( - system, - context.environment.world_cell, - ) - { - (context.error_handler)( - err, - ErrorContext::System { - name: system.name(), - last_run: system.get_last_run(), - }, - ); - } - }; - })); - context.system_completed(system_index, res, system); - }; - - if system_meta.is_send { - context.scope.spawn(task); - } else { - self.local_thread_running = true; - context.scope.spawn_on_external(task); - } - } - - /// # Safety - /// Caller must ensure no systems are currently borrowed. - unsafe fn spawn_exclusive_system_task(&mut self, context: &Context, system_index: usize) { - // SAFETY: this system is not running, no other reference exists - let system = &mut unsafe { &mut *context.environment.systems[system_index].get() }.system; - // Move the full context object into the new future. - let context = *context; - - if is_apply_deferred(system) { - // TODO: avoid allocation - let unapplied_systems = self.unapplied_systems.clone(); - self.unapplied_systems.clear(); - let task = async move { - // SAFETY: `can_run` returned true for this system, which means - // that no other systems currently have access to the world. - let world = unsafe { context.environment.world_cell.world_mut() }; - let res = apply_deferred(&unapplied_systems, context.environment.systems, world); - context.system_completed(system_index, res, system); - }; - - context.scope.spawn_on_scope(task); - } else { - let task = async move { - // SAFETY: `can_run` returned true for this system, which means - // that no other systems currently have access to the world. - let world = unsafe { context.environment.world_cell.world_mut() }; - let res = std::panic::catch_unwind(AssertUnwindSafe(|| { - if let Err(RunSystemError::Failed(err)) = - __rust_begin_short_backtrace::run(system, world) - { - (context.error_handler)( - err, - ErrorContext::System { - name: system.name(), - last_run: system.get_last_run(), - }, - ); - } - })); - context.system_completed(system_index, res, system); - }; - - context.scope.spawn_on_scope(task); - } - - self.exclusive_running = true; - self.local_thread_running = true; - } - - fn finish_system_and_handle_dependents(&mut self, result: SystemResult) { - let SystemResult { system_index, .. } = result; - - if self.system_task_metadata[system_index].is_exclusive { - self.exclusive_running = false; - } - - if !self.system_task_metadata[system_index].is_send { - self.local_thread_running = false; - } - - debug_assert!(self.num_running_systems >= 1); - self.num_running_systems -= 1; - self.running_systems.remove(system_index); - self.completed_systems.insert(system_index); - self.unapplied_systems.insert(system_index); - - self.signal_dependents(system_index); - } - - fn skip_system_and_signal_dependents(&mut self, system_index: usize) { - self.completed_systems.insert(system_index); - self.signal_dependents(system_index); - } - - fn signal_dependents(&mut self, system_index: usize) { - for &dep_idx in &self.system_task_metadata[system_index].dependents { - let remaining = &mut self.num_dependencies_remaining[dep_idx]; - debug_assert!(*remaining >= 1); - *remaining -= 1; - if *remaining == 0 && !self.completed_systems.contains(dep_idx) { - self.ready_systems.insert(dep_idx); - } - } - } -} - -fn apply_deferred( - unapplied_systems: &FixedBitSet, - systems: &[SyncUnsafeCell], - world: &mut World, -) -> Result<(), Box> { - for system_index in unapplied_systems.ones() { - // SAFETY: none of these systems are running, no other references exist - let system = &mut unsafe { &mut *systems[system_index].get() }.system; - let res = std::panic::catch_unwind(AssertUnwindSafe(|| { - system.apply_deferred(world); - })); - if let Err(payload) = res { - #[cfg(feature = "std")] - #[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")] - { - eprintln!( - "Encountered a panic when applying buffers for system `{}`!", - system.name() - ); - } - return Err(payload); - } - } - Ok(()) -} - -/// # Safety -/// - `world` must have permission to read any world data -/// required by `conditions`. -unsafe fn evaluate_and_fold_conditions( - conditions: &mut [ConditionWithAccess], - world: UnsafeWorldCell, - error_handler: ErrorHandler, -) -> bool { - #[expect( - clippy::unnecessary_fold, - reason = "Short-circuiting here would prevent conditions from mutating their own state as needed." - )] - conditions - .iter_mut() - .map(|ConditionWithAccess { condition, .. }| { - // SAFETY: - // - The caller ensures that `world` has permission to read any data - // required by the condition. - unsafe { condition.validate_param_unsafe(world) } - .map_err(From::from) - .and_then(|()| { - // SAFETY: - // - The caller ensures that `world` has permission to read any data - // required by the condition. - // - `update_archetype_component_access` has been called for condition. - unsafe { - __rust_begin_short_backtrace::readonly_run_unsafe(&mut **condition, world) - } - }) - .unwrap_or_else(|err| { - if let RunSystemError::Failed(err) = err { - error_handler( - err, - ErrorContext::System { - name: condition.name(), - last_run: condition.get_last_run(), - }, - ); - }; - false - }) - }) - .fold(true, |acc, res| acc && res) -} - -/// New-typed [`ThreadExecutor`] [`Resource`] that is used to run systems on the main thread -#[derive(Resource, Clone)] -pub struct MainThreadExecutor(pub Arc>); - -impl Default for MainThreadExecutor { - fn default() -> Self { - Self::new() - } -} - -impl MainThreadExecutor { - /// Creates a new executor that can be used to run systems on the main thread. - pub fn new() -> Self { - MainThreadExecutor(TaskPool::get_thread_executor()) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - prelude::Resource, - schedule::{ExecutorKind, IntoScheduleConfigs, Schedule}, - system::Commands, - world::World, - }; - - #[derive(Resource)] - struct R; - - #[test] - fn skipped_systems_notify_dependents() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - schedule.set_executor_kind(ExecutorKind::MultiThreaded); - schedule.add_systems( - ( - (|| {}).run_if(|| false), - // This system depends on a system that is always skipped. - |mut commands: Commands| { - commands.insert_resource(R); - }, - ) - .chain(), - ); - schedule.run(&mut world); - assert!(world.get_resource::().is_some()); - } - - /// Regression test for a weird bug flagged by MIRI in - /// `spawn_exclusive_system_task`, related to a `&mut World` being captured - /// inside an `async` block and somehow remaining alive even after its last use. - #[test] - fn check_spawn_exclusive_system_task_miri() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - schedule.set_executor_kind(ExecutorKind::MultiThreaded); - schedule.add_systems(((|_: Commands| {}), |_: Commands| {}).chain()); - schedule.run(&mut world); - } -} diff --git a/src/schedule/executor/simple.rs b/src/schedule/executor/simple.rs deleted file mode 100644 index 17f3f3b..0000000 --- a/src/schedule/executor/simple.rs +++ /dev/null @@ -1,234 +0,0 @@ -#![expect(deprecated, reason = "Everything here is deprecated")] - -use core::panic::AssertUnwindSafe; -use fixedbitset::FixedBitSet; - -#[cfg(feature = "trace")] -use tracing::info_span; - -#[cfg(feature = "std")] -use std::eprintln; - -use crate::{ - error::{ErrorContext, ErrorHandler}, - schedule::{ - executor::is_apply_deferred, ConditionWithAccess, ExecutorKind, SystemExecutor, - SystemSchedule, - }, - system::RunSystemError, - world::World, -}; -#[cfg(feature = "hotpatching")] -use crate::{event::Events, HotPatched}; - -use super::__rust_begin_short_backtrace; - -/// A variant of [`SingleThreadedExecutor`](crate::schedule::SingleThreadedExecutor) that calls -/// [`apply_deferred`](crate::system::System::apply_deferred) immediately after running each system. -#[derive(Default)] -#[deprecated( - since = "0.17.0", - note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation." -)] -pub struct SimpleExecutor { - /// Systems sets whose conditions have been evaluated. - evaluated_sets: FixedBitSet, - /// Systems that have run or been skipped. - completed_systems: FixedBitSet, -} - -impl SystemExecutor for SimpleExecutor { - fn kind(&self) -> ExecutorKind { - ExecutorKind::Simple - } - - fn init(&mut self, schedule: &SystemSchedule) { - let sys_count = schedule.system_ids.len(); - let set_count = schedule.set_ids.len(); - self.evaluated_sets = FixedBitSet::with_capacity(set_count); - self.completed_systems = FixedBitSet::with_capacity(sys_count); - } - - fn run( - &mut self, - schedule: &mut SystemSchedule, - world: &mut World, - _skip_systems: Option<&FixedBitSet>, - error_handler: ErrorHandler, - ) { - // If stepping is enabled, make sure we skip those systems that should - // not be run. - #[cfg(feature = "bevy_debug_stepping")] - if let Some(skipped_systems) = _skip_systems { - // mark skipped systems as completed - self.completed_systems |= skipped_systems; - } - - #[cfg(feature = "hotpatching")] - let should_update_hotpatch = !world - .get_resource::>() - .map(Events::is_empty) - .unwrap_or(true); - - for system_index in 0..schedule.systems.len() { - #[cfg(feature = "trace")] - let name = schedule.systems[system_index].system.name(); - #[cfg(feature = "trace")] - let should_run_span = info_span!("check_conditions", name = name.as_string()).entered(); - - let mut should_run = !self.completed_systems.contains(system_index); - for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() { - if self.evaluated_sets.contains(set_idx) { - continue; - } - - // evaluate system set's conditions - let set_conditions_met = evaluate_and_fold_conditions( - &mut schedule.set_conditions[set_idx], - world, - error_handler, - ); - - if !set_conditions_met { - self.completed_systems - .union_with(&schedule.systems_in_sets_with_conditions[set_idx]); - } - - should_run &= set_conditions_met; - self.evaluated_sets.insert(set_idx); - } - - // evaluate system's conditions - let system_conditions_met = evaluate_and_fold_conditions( - &mut schedule.system_conditions[system_index], - world, - error_handler, - ); - - should_run &= system_conditions_met; - - let system = &mut schedule.systems[system_index].system; - - #[cfg(feature = "trace")] - should_run_span.exit(); - - #[cfg(feature = "hotpatching")] - if should_update_hotpatch { - system.refresh_hotpatch(); - } - - // system has either been skipped or will run - self.completed_systems.insert(system_index); - - if !should_run { - continue; - } - - if is_apply_deferred(system) { - continue; - } - - let f = AssertUnwindSafe(|| { - if let Err(RunSystemError::Failed(err)) = - __rust_begin_short_backtrace::run(system, world) - { - error_handler( - err, - ErrorContext::System { - name: system.name(), - last_run: system.get_last_run(), - }, - ); - } - }); - - #[cfg(feature = "std")] - #[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")] - { - if let Err(payload) = std::panic::catch_unwind(f) { - eprintln!("Encountered a panic in system `{}`!", system.name()); - std::panic::resume_unwind(payload); - } - } - - #[cfg(not(feature = "std"))] - { - (f)(); - } - } - - self.evaluated_sets.clear(); - self.completed_systems.clear(); - } - - fn set_apply_final_deferred(&mut self, _: bool) { - // do nothing. simple executor does not do a final sync - } -} - -impl SimpleExecutor { - /// Creates a new simple executor for use in a [`Schedule`](crate::schedule::Schedule). - /// This calls each system in order and immediately calls [`System::apply_deferred`](crate::system::System). - pub const fn new() -> Self { - Self { - evaluated_sets: FixedBitSet::new(), - completed_systems: FixedBitSet::new(), - } - } -} -#[deprecated( - since = "0.17.0", - note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation." -)] -fn evaluate_and_fold_conditions( - conditions: &mut [ConditionWithAccess], - world: &mut World, - error_handler: ErrorHandler, -) -> bool { - #[cfg(feature = "hotpatching")] - let should_update_hotpatch = !world - .get_resource::>() - .map(Events::is_empty) - .unwrap_or(true); - - #[expect( - clippy::unnecessary_fold, - reason = "Short-circuiting here would prevent conditions from mutating their own state as needed." - )] - conditions - .iter_mut() - .map(|ConditionWithAccess { condition, .. }| { - #[cfg(feature = "hotpatching")] - if should_update_hotpatch { - condition.refresh_hotpatch(); - } - __rust_begin_short_backtrace::readonly_run(&mut **condition, world).unwrap_or_else( - |err| { - if let RunSystemError::Failed(err) = err { - error_handler( - err, - ErrorContext::System { - name: condition.name(), - last_run: condition.get_last_run(), - }, - ); - }; - false - }, - ) - }) - .fold(true, |acc, res| acc && res) -} - -#[cfg(test)] -#[test] -fn skip_automatic_sync_points() { - // Schedules automatically insert ApplyDeferred systems, but these should - // not be executed as they only serve as markers and are not initialized - use crate::prelude::*; - let mut sched = Schedule::default(); - sched.set_executor_kind(ExecutorKind::Simple); - sched.add_systems((|_: Commands| (), || ()).chain()); - let mut world = World::new(); - sched.run(&mut world); -} diff --git a/src/schedule/executor/single_threaded.rs b/src/schedule/executor/single_threaded.rs deleted file mode 100644 index 4d321bd..0000000 --- a/src/schedule/executor/single_threaded.rs +++ /dev/null @@ -1,237 +0,0 @@ -use core::panic::AssertUnwindSafe; -use fixedbitset::FixedBitSet; - -#[cfg(feature = "trace")] -use tracing::info_span; - -#[cfg(feature = "std")] -use std::eprintln; - -use crate::{ - error::{ErrorContext, ErrorHandler}, - schedule::{ - is_apply_deferred, ConditionWithAccess, ExecutorKind, SystemExecutor, SystemSchedule, - }, - system::RunSystemError, - world::World, -}; -#[cfg(feature = "hotpatching")] -use crate::{event::Events, HotPatched}; - -use super::__rust_begin_short_backtrace; - -/// Runs the schedule using a single thread. -/// -/// Useful if you're dealing with a single-threaded environment, saving your threads for -/// other things, or just trying minimize overhead. -#[derive(Default)] -pub struct SingleThreadedExecutor { - /// System sets whose conditions have been evaluated. - evaluated_sets: FixedBitSet, - /// Systems that have run or been skipped. - completed_systems: FixedBitSet, - /// Systems that have run but have not had their buffers applied. - unapplied_systems: FixedBitSet, - /// Setting when true applies deferred system buffers after all systems have run - apply_final_deferred: bool, -} - -impl SystemExecutor for SingleThreadedExecutor { - fn kind(&self) -> ExecutorKind { - ExecutorKind::SingleThreaded - } - - fn init(&mut self, schedule: &SystemSchedule) { - // pre-allocate space - let sys_count = schedule.system_ids.len(); - let set_count = schedule.set_ids.len(); - self.evaluated_sets = FixedBitSet::with_capacity(set_count); - self.completed_systems = FixedBitSet::with_capacity(sys_count); - self.unapplied_systems = FixedBitSet::with_capacity(sys_count); - } - - fn run( - &mut self, - schedule: &mut SystemSchedule, - world: &mut World, - _skip_systems: Option<&FixedBitSet>, - error_handler: ErrorHandler, - ) { - // If stepping is enabled, make sure we skip those systems that should - // not be run. - #[cfg(feature = "bevy_debug_stepping")] - if let Some(skipped_systems) = _skip_systems { - // mark skipped systems as completed - self.completed_systems |= skipped_systems; - } - - #[cfg(feature = "hotpatching")] - let should_update_hotpatch = !world - .get_resource::>() - .map(Events::is_empty) - .unwrap_or(true); - - for system_index in 0..schedule.systems.len() { - #[cfg(feature = "trace")] - let name = schedule.systems[system_index].system.name(); - #[cfg(feature = "trace")] - let should_run_span = info_span!("check_conditions", name = name.as_string()).entered(); - - let mut should_run = !self.completed_systems.contains(system_index); - for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() { - if self.evaluated_sets.contains(set_idx) { - continue; - } - - // evaluate system set's conditions - let set_conditions_met = evaluate_and_fold_conditions( - &mut schedule.set_conditions[set_idx], - world, - error_handler, - ); - - if !set_conditions_met { - self.completed_systems - .union_with(&schedule.systems_in_sets_with_conditions[set_idx]); - } - - should_run &= set_conditions_met; - self.evaluated_sets.insert(set_idx); - } - - // evaluate system's conditions - let system_conditions_met = evaluate_and_fold_conditions( - &mut schedule.system_conditions[system_index], - world, - error_handler, - ); - - should_run &= system_conditions_met; - - let system = &mut schedule.systems[system_index].system; - - #[cfg(feature = "trace")] - should_run_span.exit(); - - #[cfg(feature = "hotpatching")] - if should_update_hotpatch { - system.refresh_hotpatch(); - } - - // system has either been skipped or will run - self.completed_systems.insert(system_index); - - if !should_run { - continue; - } - - if is_apply_deferred(system) { - self.apply_deferred(schedule, world); - continue; - } - - let f = AssertUnwindSafe(|| { - if let Err(RunSystemError::Failed(err)) = - __rust_begin_short_backtrace::run_without_applying_deferred(system, world) - { - error_handler( - err, - ErrorContext::System { - name: system.name(), - last_run: system.get_last_run(), - }, - ); - } - }); - - #[cfg(feature = "std")] - #[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")] - { - if let Err(payload) = std::panic::catch_unwind(f) { - eprintln!("Encountered a panic in system `{}`!", system.name()); - std::panic::resume_unwind(payload); - } - } - - #[cfg(not(feature = "std"))] - { - (f)(); - } - - self.unapplied_systems.insert(system_index); - } - - if self.apply_final_deferred { - self.apply_deferred(schedule, world); - } - self.evaluated_sets.clear(); - self.completed_systems.clear(); - } - - fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) { - self.apply_final_deferred = apply_final_deferred; - } -} - -impl SingleThreadedExecutor { - /// Creates a new single-threaded executor for use in a [`Schedule`]. - /// - /// [`Schedule`]: crate::schedule::Schedule - pub const fn new() -> Self { - Self { - evaluated_sets: FixedBitSet::new(), - completed_systems: FixedBitSet::new(), - unapplied_systems: FixedBitSet::new(), - apply_final_deferred: true, - } - } - - fn apply_deferred(&mut self, schedule: &mut SystemSchedule, world: &mut World) { - for system_index in self.unapplied_systems.ones() { - let system = &mut schedule.systems[system_index].system; - system.apply_deferred(world); - } - - self.unapplied_systems.clear(); - } -} - -fn evaluate_and_fold_conditions( - conditions: &mut [ConditionWithAccess], - world: &mut World, - error_handler: ErrorHandler, -) -> bool { - #[cfg(feature = "hotpatching")] - let should_update_hotpatch = !world - .get_resource::>() - .map(Events::is_empty) - .unwrap_or(true); - - #[expect( - clippy::unnecessary_fold, - reason = "Short-circuiting here would prevent conditions from mutating their own state as needed." - )] - conditions - .iter_mut() - .map(|ConditionWithAccess { condition, .. }| { - #[cfg(feature = "hotpatching")] - if should_update_hotpatch { - condition.refresh_hotpatch(); - } - __rust_begin_short_backtrace::readonly_run(&mut **condition, world).unwrap_or_else( - |err| { - if let RunSystemError::Failed(err) = err { - error_handler( - err, - ErrorContext::System { - name: condition.name(), - last_run: condition.get_last_run(), - }, - ); - }; - false - }, - ) - }) - .fold(true, |acc, res| acc && res) -} diff --git a/src/schedule/graph/graph_map.rs b/src/schedule/graph/graph_map.rs deleted file mode 100644 index 9224efe..0000000 --- a/src/schedule/graph/graph_map.rs +++ /dev/null @@ -1,501 +0,0 @@ -//! `Graph` is a graph datastructure where node values are mapping -//! keys. -//! Based on the `GraphMap` datastructure from [`petgraph`]. -//! -//! [`petgraph`]: https://docs.rs/petgraph/0.6.5/petgraph/ - -use alloc::vec::Vec; -use bevy_platform::{collections::HashSet, hash::FixedHasher}; -use core::{ - fmt, - hash::{BuildHasher, Hash}, -}; -use indexmap::IndexMap; -use slotmap::{Key, KeyData}; -use smallvec::SmallVec; - -use super::NodeId; - -use Direction::{Incoming, Outgoing}; - -/// A `Graph` with undirected edges. -/// -/// For example, an edge between *1* and *2* is equivalent to an edge between -/// *2* and *1*. -pub type UnGraph = Graph; - -/// A `Graph` with directed edges. -/// -/// For example, an edge from *1* to *2* is distinct from an edge from *2* to -/// *1*. -pub type DiGraph = Graph; - -/// `Graph` is a graph datastructure using an associative array -/// of its node weights `NodeId`. -/// -/// It uses a combined adjacency list and sparse adjacency matrix -/// representation, using **O(|N| + |E|)** space, and allows testing for edge -/// existence in constant time. -/// -/// `Graph` is parameterized over: -/// -/// - Constant generic bool `DIRECTED` determines whether the graph edges are directed or -/// undirected. -/// - The `BuildHasher` `S`. -/// -/// You can use the type aliases `UnGraph` and `DiGraph` for convenience. -/// -/// `Graph` does not allow parallel edges, but self loops are allowed. -#[derive(Clone)] -pub struct Graph -where - S: BuildHasher, -{ - nodes: IndexMap, S>, - edges: HashSet, -} - -impl fmt::Debug for Graph { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.nodes.fmt(f) - } -} - -impl Graph -where - S: BuildHasher, -{ - /// Create a new `Graph` with estimated capacity. - pub fn with_capacity(nodes: usize, edges: usize) -> Self - where - S: Default, - { - Self { - nodes: IndexMap::with_capacity_and_hasher(nodes, S::default()), - edges: HashSet::with_capacity_and_hasher(edges, S::default()), - } - } - - /// Use their natural order to map the node pair (a, b) to a canonical edge id. - #[inline] - fn edge_key(a: NodeId, b: NodeId) -> CompactNodeIdPair { - let (a, b) = if DIRECTED || a <= b { (a, b) } else { (b, a) }; - - CompactNodeIdPair::store(a, b) - } - - /// Return the number of nodes in the graph. - pub fn node_count(&self) -> usize { - self.nodes.len() - } - - /// Add node `n` to the graph. - pub fn add_node(&mut self, n: NodeId) { - self.nodes.entry(n).or_default(); - } - - /// Remove a node `n` from the graph. - /// - /// Computes in **O(N)** time, due to the removal of edges with other nodes. - pub fn remove_node(&mut self, n: NodeId) { - let Some(links) = self.nodes.swap_remove(&n) else { - return; - }; - - let links = links.into_iter().map(CompactNodeIdAndDirection::load); - - for (succ, dir) in links { - let edge = if dir == Outgoing { - Self::edge_key(n, succ) - } else { - Self::edge_key(succ, n) - }; - // remove all successor links - self.remove_single_edge(succ, n, dir.opposite()); - // Remove all edge values - self.edges.remove(&edge); - } - } - - /// Return `true` if the node is contained in the graph. - pub fn contains_node(&self, n: NodeId) -> bool { - self.nodes.contains_key(&n) - } - - /// Add an edge connecting `a` and `b` to the graph. - /// For a directed graph, the edge is directed from `a` to `b`. - /// - /// Inserts nodes `a` and/or `b` if they aren't already part of the graph. - pub fn add_edge(&mut self, a: NodeId, b: NodeId) { - if self.edges.insert(Self::edge_key(a, b)) { - // insert in the adjacency list if it's a new edge - self.nodes - .entry(a) - .or_insert_with(|| Vec::with_capacity(1)) - .push(CompactNodeIdAndDirection::store(b, Outgoing)); - if a != b { - // self loops don't have the Incoming entry - self.nodes - .entry(b) - .or_insert_with(|| Vec::with_capacity(1)) - .push(CompactNodeIdAndDirection::store(a, Incoming)); - } - } - } - - /// Remove edge relation from a to b - /// - /// Return `true` if it did exist. - fn remove_single_edge(&mut self, a: NodeId, b: NodeId, dir: Direction) -> bool { - let Some(sus) = self.nodes.get_mut(&a) else { - return false; - }; - - let Some(index) = sus - .iter() - .copied() - .map(CompactNodeIdAndDirection::load) - .position(|elt| (DIRECTED && elt == (b, dir)) || (!DIRECTED && elt.0 == b)) - else { - return false; - }; - - sus.swap_remove(index); - true - } - - /// Remove edge from `a` to `b` from the graph. - /// - /// Return `false` if the edge didn't exist. - pub fn remove_edge(&mut self, a: NodeId, b: NodeId) -> bool { - let exist1 = self.remove_single_edge(a, b, Outgoing); - let exist2 = if a != b { - self.remove_single_edge(b, a, Incoming) - } else { - exist1 - }; - let weight = self.edges.remove(&Self::edge_key(a, b)); - debug_assert!(exist1 == exist2 && exist1 == weight); - weight - } - - /// Return `true` if the edge connecting `a` with `b` is contained in the graph. - pub fn contains_edge(&self, a: NodeId, b: NodeId) -> bool { - self.edges.contains(&Self::edge_key(a, b)) - } - - /// Return an iterator over the nodes of the graph. - pub fn nodes( - &self, - ) -> impl DoubleEndedIterator + ExactSizeIterator + '_ { - self.nodes.keys().copied() - } - - /// Return an iterator of all nodes with an edge starting from `a`. - pub fn neighbors(&self, a: NodeId) -> impl DoubleEndedIterator + '_ { - let iter = match self.nodes.get(&a) { - Some(neigh) => neigh.iter(), - None => [].iter(), - }; - - iter.copied() - .map(CompactNodeIdAndDirection::load) - .filter_map(|(n, dir)| (!DIRECTED || dir == Outgoing).then_some(n)) - } - - /// Return an iterator of all neighbors that have an edge between them and - /// `a`, in the specified direction. - /// If the graph's edges are undirected, this is equivalent to *.neighbors(a)*. - pub fn neighbors_directed( - &self, - a: NodeId, - dir: Direction, - ) -> impl DoubleEndedIterator + '_ { - let iter = match self.nodes.get(&a) { - Some(neigh) => neigh.iter(), - None => [].iter(), - }; - - iter.copied() - .map(CompactNodeIdAndDirection::load) - .filter_map(move |(n, d)| (!DIRECTED || d == dir || n == a).then_some(n)) - } - - /// Return an iterator of target nodes with an edge starting from `a`, - /// paired with their respective edge weights. - pub fn edges(&self, a: NodeId) -> impl DoubleEndedIterator + '_ { - self.neighbors(a) - .map(move |b| match self.edges.get(&Self::edge_key(a, b)) { - None => unreachable!(), - Some(_) => (a, b), - }) - } - - /// Return an iterator of target nodes with an edge starting from `a`, - /// paired with their respective edge weights. - pub fn edges_directed( - &self, - a: NodeId, - dir: Direction, - ) -> impl DoubleEndedIterator + '_ { - self.neighbors_directed(a, dir).map(move |b| { - let (a, b) = if dir == Incoming { (b, a) } else { (a, b) }; - - match self.edges.get(&Self::edge_key(a, b)) { - None => unreachable!(), - Some(_) => (a, b), - } - }) - } - - /// Return an iterator over all edges of the graph with their weight in arbitrary order. - pub fn all_edges(&self) -> impl ExactSizeIterator + '_ { - self.edges.iter().copied().map(CompactNodeIdPair::load) - } - - pub(crate) fn to_index(&self, ix: NodeId) -> usize { - self.nodes.get_index_of(&ix).unwrap() - } -} - -/// Create a new empty `Graph`. -impl Default for Graph -where - S: BuildHasher + Default, -{ - fn default() -> Self { - Self::with_capacity(0, 0) - } -} - -impl DiGraph { - /// Iterate over all *Strongly Connected Components* in this graph. - pub(crate) fn iter_sccs(&self) -> impl Iterator> + '_ { - super::tarjan_scc::new_tarjan_scc(self) - } -} - -/// Edge direction. -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] -#[repr(u8)] -pub enum Direction { - /// An `Outgoing` edge is an outward edge *from* the current node. - Outgoing = 0, - /// An `Incoming` edge is an inbound edge *to* the current node. - Incoming = 1, -} - -impl Direction { - /// Return the opposite `Direction`. - #[inline] - pub fn opposite(self) -> Self { - match self { - Self::Outgoing => Self::Incoming, - Self::Incoming => Self::Outgoing, - } - } -} - -/// Compact storage of a [`NodeId`] and a [`Direction`]. -#[derive(Clone, Copy)] -struct CompactNodeIdAndDirection { - key: KeyData, - is_system: bool, - direction: Direction, -} - -impl fmt::Debug for CompactNodeIdAndDirection { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.load().fmt(f) - } -} - -impl CompactNodeIdAndDirection { - fn store(node: NodeId, direction: Direction) -> Self { - let key = match node { - NodeId::System(key) => key.data(), - NodeId::Set(key) => key.data(), - }; - let is_system = node.is_system(); - - Self { - key, - is_system, - direction, - } - } - - fn load(self) -> (NodeId, Direction) { - let Self { - key, - is_system, - direction, - } = self; - - let node = match is_system { - true => NodeId::System(key.into()), - false => NodeId::Set(key.into()), - }; - - (node, direction) - } -} - -/// Compact storage of a [`NodeId`] pair. -#[derive(Clone, Copy, Hash, PartialEq, Eq)] -struct CompactNodeIdPair { - key_a: KeyData, - key_b: KeyData, - is_system_a: bool, - is_system_b: bool, -} - -impl fmt::Debug for CompactNodeIdPair { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.load().fmt(f) - } -} - -impl CompactNodeIdPair { - fn store(a: NodeId, b: NodeId) -> Self { - let key_a = match a { - NodeId::System(index) => index.data(), - NodeId::Set(index) => index.data(), - }; - let is_system_a = a.is_system(); - - let key_b = match b { - NodeId::System(index) => index.data(), - NodeId::Set(index) => index.data(), - }; - let is_system_b = b.is_system(); - - Self { - key_a, - key_b, - is_system_a, - is_system_b, - } - } - - fn load(self) -> (NodeId, NodeId) { - let Self { - key_a, - key_b, - is_system_a, - is_system_b, - } = self; - - let a = match is_system_a { - true => NodeId::System(key_a.into()), - false => NodeId::Set(key_a.into()), - }; - - let b = match is_system_b { - true => NodeId::System(key_b.into()), - false => NodeId::Set(key_b.into()), - }; - - (a, b) - } -} - -#[cfg(test)] -mod tests { - use crate::schedule::SystemKey; - - use super::*; - use alloc::vec; - use slotmap::SlotMap; - - /// The `Graph` type _must_ preserve the order that nodes are inserted in if - /// no removals occur. Removals are permitted to swap the latest node into the - /// location of the removed node. - #[test] - fn node_order_preservation() { - use NodeId::System; - - let mut slotmap = SlotMap::::with_key(); - let mut graph = ::default(); - - let sys1 = slotmap.insert(()); - let sys2 = slotmap.insert(()); - let sys3 = slotmap.insert(()); - let sys4 = slotmap.insert(()); - - graph.add_node(System(sys1)); - graph.add_node(System(sys2)); - graph.add_node(System(sys3)); - graph.add_node(System(sys4)); - - assert_eq!( - graph.nodes().collect::>(), - vec![System(sys1), System(sys2), System(sys3), System(sys4)] - ); - - graph.remove_node(System(sys1)); - - assert_eq!( - graph.nodes().collect::>(), - vec![System(sys4), System(sys2), System(sys3)] - ); - - graph.remove_node(System(sys4)); - - assert_eq!( - graph.nodes().collect::>(), - vec![System(sys3), System(sys2)] - ); - - graph.remove_node(System(sys2)); - - assert_eq!(graph.nodes().collect::>(), vec![System(sys3)]); - - graph.remove_node(System(sys3)); - - assert_eq!(graph.nodes().collect::>(), vec![]); - } - - /// Nodes that have bidirectional edges (or any edge in the case of undirected graphs) are - /// considered strongly connected. A strongly connected component is a collection of - /// nodes where there exists a path from any node to any other node in the collection. - #[test] - fn strongly_connected_components() { - use NodeId::System; - - let mut slotmap = SlotMap::::with_key(); - let mut graph = ::default(); - - let sys1 = slotmap.insert(()); - let sys2 = slotmap.insert(()); - let sys3 = slotmap.insert(()); - let sys4 = slotmap.insert(()); - let sys5 = slotmap.insert(()); - let sys6 = slotmap.insert(()); - - graph.add_edge(System(sys1), System(sys2)); - graph.add_edge(System(sys2), System(sys1)); - - graph.add_edge(System(sys2), System(sys3)); - graph.add_edge(System(sys3), System(sys2)); - - graph.add_edge(System(sys4), System(sys5)); - graph.add_edge(System(sys5), System(sys4)); - - graph.add_edge(System(sys6), System(sys2)); - - let sccs = graph - .iter_sccs() - .map(|scc| scc.to_vec()) - .collect::>(); - - assert_eq!( - sccs, - vec![ - vec![System(sys3), System(sys2), System(sys1)], - vec![System(sys5), System(sys4)], - vec![System(sys6)] - ] - ); - } -} diff --git a/src/schedule/graph/mod.rs b/src/schedule/graph/mod.rs deleted file mode 100644 index 8a98604..0000000 --- a/src/schedule/graph/mod.rs +++ /dev/null @@ -1,331 +0,0 @@ -use alloc::{boxed::Box, vec, vec::Vec}; -use core::{ - any::{Any, TypeId}, - fmt::Debug, -}; -use smallvec::SmallVec; - -use bevy_platform::collections::{HashMap, HashSet}; -use bevy_utils::TypeIdMap; - -use fixedbitset::FixedBitSet; - -use crate::schedule::set::*; - -mod graph_map; -mod node; -mod tarjan_scc; - -pub use graph_map::{DiGraph, Direction, UnGraph}; -pub use node::NodeId; - -/// Specifies what kind of edge should be added to the dependency graph. -#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub(crate) enum DependencyKind { - /// A node that should be preceded. - Before, - /// A node that should be succeeded. - After, -} - -/// An edge to be added to the dependency graph. -pub(crate) struct Dependency { - pub(crate) kind: DependencyKind, - pub(crate) set: InternedSystemSet, - pub(crate) options: TypeIdMap>, -} - -impl Dependency { - pub fn new(kind: DependencyKind, set: InternedSystemSet) -> Self { - Self { - kind, - set, - options: Default::default(), - } - } - pub fn add_config(mut self, option: T) -> Self { - self.options.insert(TypeId::of::(), Box::new(option)); - self - } -} - -/// Configures ambiguity detection for a single system. -#[derive(Clone, Debug, Default)] -pub(crate) enum Ambiguity { - #[default] - Check, - /// Ignore warnings with systems in any of these system sets. May contain duplicates. - IgnoreWithSet(Vec), - /// Ignore all warnings. - IgnoreAll, -} - -/// Metadata about how the node fits in the schedule graph -#[derive(Default)] -pub struct GraphInfo { - /// the sets that the node belongs to (hierarchy) - pub(crate) hierarchy: Vec, - /// the sets that the node depends on (must run before or after) - pub(crate) dependencies: Vec, - pub(crate) ambiguous_with: Ambiguity, -} - -/// Converts 2D row-major pair of indices into a 1D array index. -pub(crate) fn index(row: usize, col: usize, num_cols: usize) -> usize { - debug_assert!(col < num_cols); - (row * num_cols) + col -} - -/// Converts a 1D array index into a 2D row-major pair of indices. -pub(crate) fn row_col(index: usize, num_cols: usize) -> (usize, usize) { - (index / num_cols, index % num_cols) -} - -/// Stores the results of the graph analysis. -pub(crate) struct CheckGraphResults { - /// Boolean reachability matrix for the graph. - pub(crate) reachable: FixedBitSet, - /// Pairs of nodes that have a path connecting them. - pub(crate) connected: HashSet<(NodeId, NodeId)>, - /// Pairs of nodes that don't have a path connecting them. - pub(crate) disconnected: Vec<(NodeId, NodeId)>, - /// Edges that are redundant because a longer path exists. - pub(crate) transitive_edges: Vec<(NodeId, NodeId)>, - /// Variant of the graph with no transitive edges. - pub(crate) transitive_reduction: DiGraph, - /// Variant of the graph with all possible transitive edges. - // TODO: this will very likely be used by "if-needed" ordering - #[expect(dead_code, reason = "See the TODO above this attribute.")] - pub(crate) transitive_closure: DiGraph, -} - -impl Default for CheckGraphResults { - fn default() -> Self { - Self { - reachable: FixedBitSet::new(), - connected: HashSet::default(), - disconnected: Vec::new(), - transitive_edges: Vec::new(), - transitive_reduction: DiGraph::default(), - transitive_closure: DiGraph::default(), - } - } -} - -/// Processes a DAG and computes its: -/// - transitive reduction (along with the set of removed edges) -/// - transitive closure -/// - reachability matrix (as a bitset) -/// - pairs of nodes connected by a path -/// - pairs of nodes not connected by a path -/// -/// The algorithm implemented comes from -/// ["On the calculation of transitive reduction-closure of orders"][1] by Habib, Morvan and Rampon. -/// -/// [1]: https://doi.org/10.1016/0012-365X(93)90164-O -pub(crate) fn check_graph(graph: &DiGraph, topological_order: &[NodeId]) -> CheckGraphResults { - if graph.node_count() == 0 { - return CheckGraphResults::default(); - } - - let n = graph.node_count(); - - // build a copy of the graph where the nodes and edges appear in topsorted order - let mut map = >::with_capacity_and_hasher(n, Default::default()); - let mut topsorted = ::default(); - // iterate nodes in topological order - for (i, &node) in topological_order.iter().enumerate() { - map.insert(node, i); - topsorted.add_node(node); - // insert nodes as successors to their predecessors - for pred in graph.neighbors_directed(node, Direction::Incoming) { - topsorted.add_edge(pred, node); - } - } - - let mut reachable = FixedBitSet::with_capacity(n * n); - let mut connected = >::default(); - let mut disconnected = Vec::new(); - - let mut transitive_edges = Vec::new(); - let mut transitive_reduction = DiGraph::default(); - let mut transitive_closure = DiGraph::default(); - - let mut visited = FixedBitSet::with_capacity(n); - - // iterate nodes in topological order - for node in topsorted.nodes() { - transitive_reduction.add_node(node); - transitive_closure.add_node(node); - } - - // iterate nodes in reverse topological order - for a in topsorted.nodes().rev() { - let index_a = *map.get(&a).unwrap(); - // iterate their successors in topological order - for b in topsorted.neighbors_directed(a, Direction::Outgoing) { - let index_b = *map.get(&b).unwrap(); - debug_assert!(index_a < index_b); - if !visited[index_b] { - // edge is not redundant - transitive_reduction.add_edge(a, b); - transitive_closure.add_edge(a, b); - reachable.insert(index(index_a, index_b, n)); - - let successors = transitive_closure - .neighbors_directed(b, Direction::Outgoing) - .collect::>(); - for c in successors { - let index_c = *map.get(&c).unwrap(); - debug_assert!(index_b < index_c); - if !visited[index_c] { - visited.insert(index_c); - transitive_closure.add_edge(a, c); - reachable.insert(index(index_a, index_c, n)); - } - } - } else { - // edge is redundant - transitive_edges.push((a, b)); - } - } - - visited.clear(); - } - - // partition pairs of nodes into "connected by path" and "not connected by path" - for i in 0..(n - 1) { - // reachable is upper triangular because the nodes were topsorted - for index in index(i, i + 1, n)..=index(i, n - 1, n) { - let (a, b) = row_col(index, n); - let pair = (topological_order[a], topological_order[b]); - if reachable[index] { - connected.insert(pair); - } else { - disconnected.push(pair); - } - } - } - - // fill diagonal (nodes reach themselves) - // for i in 0..n { - // reachable.set(index(i, i, n), true); - // } - - CheckGraphResults { - reachable, - connected, - disconnected, - transitive_edges, - transitive_reduction, - transitive_closure, - } -} - -/// Returns the simple cycles in a strongly-connected component of a directed graph. -/// -/// The algorithm implemented comes from -/// ["Finding all the elementary circuits of a directed graph"][1] by D. B. Johnson. -/// -/// [1]: https://doi.org/10.1137/0204007 -pub fn simple_cycles_in_component(graph: &DiGraph, scc: &[NodeId]) -> Vec> { - let mut cycles = vec![]; - let mut sccs = vec![SmallVec::from_slice(scc)]; - - while let Some(mut scc) = sccs.pop() { - // only look at nodes and edges in this strongly-connected component - let mut subgraph = ::default(); - for &node in &scc { - subgraph.add_node(node); - } - - for &node in &scc { - for successor in graph.neighbors(node) { - if subgraph.contains_node(successor) { - subgraph.add_edge(node, successor); - } - } - } - - // path of nodes that may form a cycle - let mut path = Vec::with_capacity(subgraph.node_count()); - // we mark nodes as "blocked" to avoid finding permutations of the same cycles - let mut blocked: HashSet<_> = - HashSet::with_capacity_and_hasher(subgraph.node_count(), Default::default()); - // connects nodes along path segments that can't be part of a cycle (given current root) - // those nodes can be unblocked at the same time - let mut unblock_together: HashMap> = - HashMap::with_capacity_and_hasher(subgraph.node_count(), Default::default()); - // stack for unblocking nodes - let mut unblock_stack = Vec::with_capacity(subgraph.node_count()); - // nodes can be involved in multiple cycles - let mut maybe_in_more_cycles: HashSet = - HashSet::with_capacity_and_hasher(subgraph.node_count(), Default::default()); - // stack for DFS - let mut stack = Vec::with_capacity(subgraph.node_count()); - - // we're going to look for all cycles that begin and end at this node - let root = scc.pop().unwrap(); - // start a path at the root - path.clear(); - path.push(root); - // mark this node as blocked - blocked.insert(root); - - // DFS - stack.clear(); - stack.push((root, subgraph.neighbors(root))); - while !stack.is_empty() { - let &mut (ref node, ref mut successors) = stack.last_mut().unwrap(); - if let Some(next) = successors.next() { - if next == root { - // found a cycle - maybe_in_more_cycles.extend(path.iter()); - cycles.push(path.clone()); - } else if !blocked.contains(&next) { - // first time seeing `next` on this path - maybe_in_more_cycles.remove(&next); - path.push(next); - blocked.insert(next); - stack.push((next, subgraph.neighbors(next))); - continue; - } else { - // not first time seeing `next` on this path - } - } - - if successors.peekable().peek().is_none() { - if maybe_in_more_cycles.contains(node) { - unblock_stack.push(*node); - // unblock this node's ancestors - while let Some(n) = unblock_stack.pop() { - if blocked.remove(&n) { - let unblock_predecessors = unblock_together.entry(n).or_default(); - unblock_stack.extend(unblock_predecessors.iter()); - unblock_predecessors.clear(); - } - } - } else { - // if its descendants can be unblocked later, this node will be too - for successor in subgraph.neighbors(*node) { - unblock_together.entry(successor).or_default().insert(*node); - } - } - - // remove node from path and DFS stack - path.pop(); - stack.pop(); - } - } - - drop(stack); - - // remove node from subgraph - subgraph.remove_node(root); - - // divide remainder into smaller SCCs - sccs.extend(subgraph.iter_sccs().filter(|scc| scc.len() > 1)); - } - - cycles -} diff --git a/src/schedule/graph/node.rs b/src/schedule/graph/node.rs deleted file mode 100644 index 40f4f53..0000000 --- a/src/schedule/graph/node.rs +++ /dev/null @@ -1,42 +0,0 @@ -use core::fmt::Debug; - -use crate::schedule::{SystemKey, SystemSetKey}; - -/// Unique identifier for a system or system set stored in a [`ScheduleGraph`]. -/// -/// [`ScheduleGraph`]: crate::schedule::ScheduleGraph -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum NodeId { - /// Identifier for a system. - System(SystemKey), - /// Identifier for a system set. - Set(SystemSetKey), -} - -impl NodeId { - /// Returns `true` if the identified node is a system. - pub const fn is_system(&self) -> bool { - matches!(self, NodeId::System(_)) - } - - /// Returns `true` if the identified node is a system set. - pub const fn is_set(&self) -> bool { - matches!(self, NodeId::Set(_)) - } - - /// Returns the system key if the node is a system, otherwise `None`. - pub const fn as_system(&self) -> Option { - match self { - NodeId::System(system) => Some(*system), - NodeId::Set(_) => None, - } - } - - /// Returns the system set key if the node is a system set, otherwise `None`. - pub const fn as_set(&self) -> Option { - match self { - NodeId::System(_) => None, - NodeId::Set(set) => Some(*set), - } - } -} diff --git a/src/schedule/graph/tarjan_scc.rs b/src/schedule/graph/tarjan_scc.rs deleted file mode 100644 index 5718dd2..0000000 --- a/src/schedule/graph/tarjan_scc.rs +++ /dev/null @@ -1,223 +0,0 @@ -use super::DiGraph; -use super::NodeId; -use alloc::vec::Vec; -use core::hash::BuildHasher; -use core::num::NonZeroUsize; -use smallvec::SmallVec; - -/// Create an iterator over *strongly connected components* using Algorithm 3 in -/// [A Space-Efficient Algorithm for Finding Strongly Connected Components][1] by David J. Pierce, -/// which is a memory-efficient variation of [Tarjan's algorithm][2]. -/// -/// -/// [1]: https://homepages.ecs.vuw.ac.nz/~djp/files/P05.pdf -/// [2]: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm -/// -/// Returns each strongly strongly connected component (scc). -/// The order of node ids within each scc is arbitrary, but the order of -/// the sccs is their postorder (reverse topological sort). -pub(crate) fn new_tarjan_scc( - graph: &DiGraph, -) -> impl Iterator> + '_ { - // Create a list of all nodes we need to visit. - let unchecked_nodes = graph.nodes(); - - // For each node we need to visit, we also need to visit its neighbors. - // Storing the iterator for each set of neighbors allows this list to be computed without - // an additional allocation. - let nodes = graph - .nodes() - .map(|node| NodeData { - root_index: None, - neighbors: graph.neighbors(node), - }) - .collect::>(); - - TarjanScc { - graph, - unchecked_nodes, - index: 1, // Invariant: index < component_count at all times. - component_count: usize::MAX, // Will hold if component_count is initialized to number of nodes - 1 or higher. - nodes, - stack: Vec::new(), - visitation_stack: Vec::new(), - start: None, - index_adjustment: None, - } -} - -struct NodeData> { - root_index: Option, - neighbors: N, -} - -/// A state for computing the *strongly connected components* using [Tarjan's algorithm][1]. -/// -/// This is based on [`TarjanScc`] from [`petgraph`]. -/// -/// [1]: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm -/// [`petgraph`]: https://docs.rs/petgraph/0.6.5/petgraph/ -/// [`TarjanScc`]: https://docs.rs/petgraph/0.6.5/petgraph/algo/struct.TarjanScc.html -struct TarjanScc<'graph, Hasher, AllNodes, Neighbors> -where - Hasher: BuildHasher, - AllNodes: Iterator, - Neighbors: Iterator, -{ - /// Source of truth [`DiGraph`] - graph: &'graph DiGraph, - /// An [`Iterator`] of [`NodeId`]s from the `graph` which may not have been visited yet. - unchecked_nodes: AllNodes, - /// The index of the next SCC - index: usize, - /// A count of potentially remaining SCCs - component_count: usize, - /// Information about each [`NodeId`], including a possible SCC index and an - /// [`Iterator`] of possibly unvisited neighbors. - nodes: Vec>, - /// A stack of [`NodeId`]s where a SCC will be found starting at the top of the stack. - stack: Vec, - /// A stack of [`NodeId`]s which need to be visited to determine which SCC they belong to. - visitation_stack: Vec<(NodeId, bool)>, - /// An index into the `stack` indicating the starting point of a SCC. - start: Option, - /// An adjustment to the `index` which will be applied once the current SCC is found. - index_adjustment: Option, -} - -impl<'graph, S: BuildHasher, A: Iterator, N: Iterator> - TarjanScc<'graph, S, A, N> -{ - /// Compute the next *strongly connected component* using Algorithm 3 in - /// [A Space-Efficient Algorithm for Finding Strongly Connected Components][1] by David J. Pierce, - /// which is a memory-efficient variation of [Tarjan's algorithm][2]. - /// - /// - /// [1]: https://homepages.ecs.vuw.ac.nz/~djp/files/P05.pdf - /// [2]: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm - /// - /// Returns `Some` for each strongly strongly connected component (scc). - /// The order of node ids within each scc is arbitrary, but the order of - /// the sccs is their postorder (reverse topological sort). - fn next_scc(&mut self) -> Option<&[NodeId]> { - // Cleanup from possible previous iteration - if let (Some(start), Some(index_adjustment)) = - (self.start.take(), self.index_adjustment.take()) - { - self.stack.truncate(start); - self.index -= index_adjustment; // Backtrack index back to where it was before we ever encountered the component. - self.component_count -= 1; - } - - loop { - // If there are items on the visitation stack, then we haven't finished visiting - // the node at the bottom of the stack yet. - // Must visit all nodes in the stack from top to bottom before visiting the next node. - while let Some((v, v_is_local_root)) = self.visitation_stack.pop() { - // If this visitation finds a complete SCC, return it immediately. - if let Some(start) = self.visit_once(v, v_is_local_root) { - return Some(&self.stack[start..]); - }; - } - - // Get the next node to check, otherwise we're done and can return None. - let Some(node) = self.unchecked_nodes.next() else { - break None; - }; - - let visited = self.nodes[self.graph.to_index(node)].root_index.is_some(); - - // If this node hasn't already been visited (e.g., it was the neighbor of a previously checked node) - // add it to the visitation stack. - if !visited { - self.visitation_stack.push((node, true)); - } - } - } - - /// Attempt to find the starting point on the stack for a new SCC without visiting neighbors. - /// If a visitation is required, this will return `None` and mark the required neighbor and the - /// current node as in need of visitation again. - /// If no SCC can be found in the current visitation stack, returns `None`. - fn visit_once(&mut self, v: NodeId, mut v_is_local_root: bool) -> Option { - let node_v = &mut self.nodes[self.graph.to_index(v)]; - - if node_v.root_index.is_none() { - let v_index = self.index; - node_v.root_index = NonZeroUsize::new(v_index); - self.index += 1; - } - - while let Some(w) = self.nodes[self.graph.to_index(v)].neighbors.next() { - // If a neighbor hasn't been visited yet... - if self.nodes[self.graph.to_index(w)].root_index.is_none() { - // Push the current node and the neighbor back onto the visitation stack. - // On the next execution of `visit_once`, the neighbor will be visited. - self.visitation_stack.push((v, v_is_local_root)); - self.visitation_stack.push((w, true)); - - return None; - } - - if self.nodes[self.graph.to_index(w)].root_index - < self.nodes[self.graph.to_index(v)].root_index - { - self.nodes[self.graph.to_index(v)].root_index = - self.nodes[self.graph.to_index(w)].root_index; - v_is_local_root = false; - } - } - - if !v_is_local_root { - self.stack.push(v); // Stack is filled up when backtracking, unlike in Tarjans original algorithm. - return None; - } - - // Pop the stack and generate an SCC. - let mut index_adjustment = 1; - let c = NonZeroUsize::new(self.component_count); - let nodes = &mut self.nodes; - let start = self - .stack - .iter() - .rposition(|&w| { - if nodes[self.graph.to_index(v)].root_index - > nodes[self.graph.to_index(w)].root_index - { - true - } else { - nodes[self.graph.to_index(w)].root_index = c; - index_adjustment += 1; - false - } - }) - .map(|x| x + 1) - .unwrap_or_default(); - nodes[self.graph.to_index(v)].root_index = c; - self.stack.push(v); // Pushing the component root to the back right before getting rid of it is somewhat ugly, but it lets it be included in f. - - self.start = Some(start); - self.index_adjustment = Some(index_adjustment); - - Some(start) - } -} - -impl<'graph, S: BuildHasher, A: Iterator, N: Iterator> Iterator - for TarjanScc<'graph, S, A, N> -{ - // It is expected that the `DiGraph` is sparse, and as such wont have many large SCCs. - // Returning a `SmallVec` allows this iterator to skip allocation in cases where that - // assumption holds. - type Item = SmallVec<[NodeId; 4]>; - - fn next(&mut self) -> Option { - let next = SmallVec::from_slice(self.next_scc()?); - Some(next) - } - - fn size_hint(&self) -> (usize, Option) { - // There can be no more than the number of nodes in a graph worth of SCCs - (0, Some(self.nodes.len())) - } -} diff --git a/src/schedule/mod.rs b/src/schedule/mod.rs deleted file mode 100644 index 80189d5..0000000 --- a/src/schedule/mod.rs +++ /dev/null @@ -1,1287 +0,0 @@ -//! Contains APIs for ordering systems and executing them on a [`World`](crate::world::World) - -mod auto_insert_apply_deferred; -mod condition; -mod config; -mod executor; -mod pass; -mod schedule; -mod set; -mod stepping; - -use self::graph::*; -pub use self::{condition::*, config::*, executor::*, schedule::*, set::*}; -pub use pass::ScheduleBuildPass; - -pub use self::graph::NodeId; - -/// An implementation of a graph data structure. -pub mod graph; - -/// Included optional schedule build passes. -pub mod passes { - pub use crate::schedule::auto_insert_apply_deferred::*; -} - -#[cfg(test)] -mod tests { - use super::*; - #[cfg(feature = "trace")] - use alloc::string::ToString; - use alloc::{vec, vec::Vec}; - use core::sync::atomic::{AtomicU32, Ordering}; - - pub use crate::{ - prelude::World, - resource::Resource, - schedule::{Schedule, SystemSet}, - system::{Res, ResMut}, - }; - - #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] - enum TestSystems { - A, - B, - C, - D, - X, - } - - #[derive(Resource, Default)] - struct SystemOrder(Vec); - - #[derive(Resource, Default)] - struct RunConditionBool(bool); - - #[derive(Resource, Default)] - struct Counter(AtomicU32); - - fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) { - move |world| world.resource_mut::().0.push(tag) - } - - fn make_function_system(tag: u32) -> impl FnMut(ResMut) { - move |mut resource: ResMut| resource.0.push(tag) - } - - fn named_system(mut resource: ResMut) { - resource.0.push(u32::MAX); - } - - fn named_exclusive_system(world: &mut World) { - world.resource_mut::().0.push(u32::MAX); - } - - fn counting_system(counter: Res) { - counter.0.fetch_add(1, Ordering::Relaxed); - } - - mod system_execution { - use super::*; - - #[test] - fn run_system() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_systems(make_function_system(0)); - schedule.run(&mut world); - - assert_eq!(world.resource::().0, vec![0]); - } - - #[test] - fn run_exclusive_system() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_systems(make_exclusive_system(0)); - schedule.run(&mut world); - - assert_eq!(world.resource::().0, vec![0]); - } - - #[test] - #[cfg(not(miri))] - fn parallel_execution() { - use alloc::sync::Arc; - use bevy_tasks::{ComputeTaskPool, TaskPool}; - use std::sync::Barrier; - - let mut world = World::default(); - let mut schedule = Schedule::default(); - let thread_count = ComputeTaskPool::get_or_init(TaskPool::default).thread_num(); - - let barrier = Arc::new(Barrier::new(thread_count)); - - for _ in 0..thread_count { - let inner = barrier.clone(); - schedule.add_systems(move || { - inner.wait(); - }); - } - - schedule.run(&mut world); - } - } - - mod system_ordering { - use super::*; - - #[test] - fn order_systems() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_systems(( - named_system, - make_function_system(1).before(named_system), - make_function_system(0) - .after(named_system) - .in_set(TestSystems::A), - )); - schedule.run(&mut world); - - assert_eq!(world.resource::().0, vec![1, u32::MAX, 0]); - - world.insert_resource(SystemOrder::default()); - - assert_eq!(world.resource::().0, vec![]); - - // modify the schedule after it's been initialized and test ordering with sets - schedule.configure_sets(TestSystems::A.after(named_system)); - schedule.add_systems(( - make_function_system(3) - .before(TestSystems::A) - .after(named_system), - make_function_system(4).after(TestSystems::A), - )); - schedule.run(&mut world); - - assert_eq!( - world.resource::().0, - vec![1, u32::MAX, 3, 0, 4] - ); - } - - #[test] - fn order_exclusive_systems() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_systems(( - named_exclusive_system, - make_exclusive_system(1).before(named_exclusive_system), - make_exclusive_system(0).after(named_exclusive_system), - )); - schedule.run(&mut world); - - assert_eq!(world.resource::().0, vec![1, u32::MAX, 0]); - } - - #[test] - fn add_systems_correct_order() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_systems( - ( - make_function_system(0), - make_function_system(1), - make_exclusive_system(2), - make_function_system(3), - ) - .chain(), - ); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 3]); - } - - #[test] - fn add_systems_correct_order_nested() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_systems( - ( - (make_function_system(0), make_function_system(1)).chain(), - make_function_system(2), - (make_function_system(3), make_function_system(4)).chain(), - ( - make_function_system(5), - (make_function_system(6), make_function_system(7)), - ), - ( - (make_function_system(8), make_function_system(9)).chain(), - make_function_system(10), - ), - ) - .chain(), - ); - - schedule.run(&mut world); - let order = &world.resource::().0; - assert_eq!( - &order[0..5], - &[0, 1, 2, 3, 4], - "first five items should be exactly ordered" - ); - let unordered = &order[5..8]; - assert!( - unordered.contains(&5) && unordered.contains(&6) && unordered.contains(&7), - "unordered must be 5, 6, and 7 in any order" - ); - let partially_ordered = &order[8..11]; - assert!( - partially_ordered == [8, 9, 10] || partially_ordered == [10, 8, 9], - "partially_ordered must be [8, 9, 10] or [10, 8, 9]" - ); - assert_eq!(order.len(), 11, "must have exactly 11 order entries"); - } - } - - mod conditions { - - use crate::{ - change_detection::DetectChanges, - error::{ignore, DefaultErrorHandler, Result}, - }; - - use super::*; - - #[test] - fn system_with_condition_bool() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - world.init_resource::(); - - schedule.add_systems( - make_function_system(0).run_if(|condition: Res| condition.0), - ); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![]); - - world.resource_mut::().0 = true; - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![0]); - } - - #[test] - fn system_with_condition_result_bool() { - let mut world = World::default(); - world.insert_resource(DefaultErrorHandler(ignore)); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_systems(( - make_function_system(0).run_if(|| -> Result { Err(core::fmt::Error.into()) }), - make_function_system(1).run_if(|| -> Result { Ok(false) }), - )); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![]); - - schedule.add_systems(make_function_system(2).run_if(|| -> Result { Ok(true) })); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![2]); - } - - #[test] - fn systems_with_distributive_condition() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.insert_resource(RunConditionBool(true)); - world.init_resource::(); - - fn change_condition(mut condition: ResMut) { - condition.0 = false; - } - - schedule.add_systems( - ( - make_function_system(0), - change_condition, - make_function_system(1), - ) - .chain() - .distributive_run_if(|condition: Res| condition.0), - ); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![0]); - } - - #[test] - fn run_exclusive_system_with_condition() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - world.init_resource::(); - - schedule.add_systems( - make_exclusive_system(0).run_if(|condition: Res| condition.0), - ); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![]); - - world.resource_mut::().0 = true; - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![0]); - } - - #[test] - fn multiple_conditions_on_system() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_systems(( - counting_system.run_if(|| false).run_if(|| false), - counting_system.run_if(|| true).run_if(|| false), - counting_system.run_if(|| false).run_if(|| true), - counting_system.run_if(|| true).run_if(|| true), - )); - - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - } - - #[test] - fn multiple_conditions_on_system_sets() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.configure_sets(TestSystems::A.run_if(|| false).run_if(|| false)); - schedule.add_systems(counting_system.in_set(TestSystems::A)); - schedule.configure_sets(TestSystems::B.run_if(|| true).run_if(|| false)); - schedule.add_systems(counting_system.in_set(TestSystems::B)); - schedule.configure_sets(TestSystems::C.run_if(|| false).run_if(|| true)); - schedule.add_systems(counting_system.in_set(TestSystems::C)); - schedule.configure_sets(TestSystems::D.run_if(|| true).run_if(|| true)); - schedule.add_systems(counting_system.in_set(TestSystems::D)); - - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - } - - #[test] - fn systems_nested_in_system_sets() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.configure_sets(TestSystems::A.run_if(|| false)); - schedule.add_systems(counting_system.in_set(TestSystems::A).run_if(|| false)); - schedule.configure_sets(TestSystems::B.run_if(|| true)); - schedule.add_systems(counting_system.in_set(TestSystems::B).run_if(|| false)); - schedule.configure_sets(TestSystems::C.run_if(|| false)); - schedule.add_systems(counting_system.in_set(TestSystems::C).run_if(|| true)); - schedule.configure_sets(TestSystems::D.run_if(|| true)); - schedule.add_systems(counting_system.in_set(TestSystems::D).run_if(|| true)); - - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - } - - #[test] - fn system_conditions_and_change_detection() { - #[derive(Resource, Default)] - struct Bool2(pub bool); - - let mut world = World::default(); - world.init_resource::(); - world.init_resource::(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - schedule.add_systems( - counting_system - .run_if(|res1: Res| res1.is_changed()) - .run_if(|res2: Res| res2.is_changed()), - ); - - // both resource were just added. - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // nothing has changed - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // RunConditionBool has changed, but counting_system did not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // internal state for the bool2 run criteria was updated in the - // previous run, so system still does not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // internal state for bool2 was updated, so system still does not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // now check that it works correctly changing Bool2 first and then RunConditionBool - world.get_resource_mut::().unwrap().0 = false; - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 2); - } - - #[test] - fn system_set_conditions_and_change_detection() { - #[derive(Resource, Default)] - struct Bool2(pub bool); - - let mut world = World::default(); - world.init_resource::(); - world.init_resource::(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - schedule.configure_sets( - TestSystems::A - .run_if(|res1: Res| res1.is_changed()) - .run_if(|res2: Res| res2.is_changed()), - ); - - schedule.add_systems(counting_system.in_set(TestSystems::A)); - - // both resource were just added. - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // nothing has changed - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // RunConditionBool has changed, but counting_system did not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // internal state for the bool2 run criteria was updated in the - // previous run, so system still does not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // internal state for bool2 was updated, so system still does not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // the system only runs when both are changed on the same run - world.get_resource_mut::().unwrap().0 = false; - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 2); - } - - #[test] - fn mixed_conditions_and_change_detection() { - #[derive(Resource, Default)] - struct Bool2(pub bool); - - let mut world = World::default(); - world.init_resource::(); - world.init_resource::(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - schedule.configure_sets( - TestSystems::A.run_if(|res1: Res| res1.is_changed()), - ); - - schedule.add_systems( - counting_system - .run_if(|res2: Res| res2.is_changed()) - .in_set(TestSystems::A), - ); - - // both resource were just added. - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // nothing has changed - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // RunConditionBool has changed, but counting_system did not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // we now only change bool2 and the system also should not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // internal state for the bool2 run criteria was updated in the - // previous run, so system still does not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // the system only runs when both are changed on the same run - world.get_resource_mut::().unwrap().0 = false; - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 2); - } - } - - mod schedule_build_errors { - use super::*; - - #[test] - #[should_panic] - fn dependency_loop() { - let mut schedule = Schedule::default(); - schedule.configure_sets(TestSystems::X.after(TestSystems::X)); - } - - #[test] - fn dependency_cycle() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.configure_sets(TestSystems::A.after(TestSystems::B)); - schedule.configure_sets(TestSystems::B.after(TestSystems::A)); - - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::DependencyCycle(_)) - )); - - fn foo() {} - fn bar() {} - - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.add_systems((foo.after(bar), bar.after(foo))); - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::DependencyCycle(_)) - )); - } - - #[test] - #[should_panic] - fn hierarchy_loop() { - let mut schedule = Schedule::default(); - schedule.configure_sets(TestSystems::X.in_set(TestSystems::X)); - } - - #[test] - fn hierarchy_cycle() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.configure_sets(TestSystems::A.in_set(TestSystems::B)); - schedule.configure_sets(TestSystems::B.in_set(TestSystems::A)); - - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(ScheduleBuildError::HierarchyCycle(_)))); - } - - #[test] - fn system_type_set_ambiguity() { - // Define some systems. - fn foo() {} - fn bar() {} - - let mut world = World::new(); - let mut schedule = Schedule::default(); - - // Schedule `bar` to run after `foo`. - schedule.add_systems((foo, bar.after(foo))); - - // There's only one `foo`, so it's fine. - let result = schedule.initialize(&mut world); - assert!(result.is_ok()); - - // Schedule another `foo`. - schedule.add_systems(foo); - - // When there are multiple instances of `foo`, dependencies on - // `foo` are no longer allowed. Too much ambiguity. - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::SystemTypeSetAmbiguity(_)) - )); - - // same goes for `ambiguous_with` - let mut schedule = Schedule::default(); - schedule.add_systems(foo); - schedule.add_systems(bar.ambiguous_with(foo)); - let result = schedule.initialize(&mut world); - assert!(result.is_ok()); - schedule.add_systems(foo); - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::SystemTypeSetAmbiguity(_)) - )); - } - - #[test] - #[should_panic] - fn configure_system_type_set() { - fn foo() {} - let mut schedule = Schedule::default(); - schedule.configure_sets(foo.into_system_set()); - } - - #[test] - fn hierarchy_redundancy() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.set_build_settings(ScheduleBuildSettings { - hierarchy_detection: LogLevel::Error, - ..Default::default() - }); - - // Add `A`. - schedule.configure_sets(TestSystems::A); - - // Add `B` as child of `A`. - schedule.configure_sets(TestSystems::B.in_set(TestSystems::A)); - - // Add `X` as child of both `A` and `B`. - schedule.configure_sets(TestSystems::X.in_set(TestSystems::A).in_set(TestSystems::B)); - - // `X` cannot be the `A`'s child and grandchild at the same time. - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::HierarchyRedundancy(_)) - )); - } - - #[test] - fn cross_dependency() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - // Add `B` and give it both kinds of relationships with `A`. - schedule.configure_sets(TestSystems::B.in_set(TestSystems::A)); - schedule.configure_sets(TestSystems::B.after(TestSystems::A)); - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::CrossDependency(_, _)) - )); - } - - #[test] - fn sets_have_order_but_intersect() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - fn foo() {} - - // Add `foo` to both `A` and `C`. - schedule.add_systems(foo.in_set(TestSystems::A).in_set(TestSystems::C)); - - // Order `A -> B -> C`. - schedule.configure_sets(( - TestSystems::A, - TestSystems::B.after(TestSystems::A), - TestSystems::C.after(TestSystems::B), - )); - - let result = schedule.initialize(&mut world); - // `foo` can't be in both `A` and `C` because they can't run at the same time. - assert!(matches!( - result, - Err(ScheduleBuildError::SetsHaveOrderButIntersect(_, _)) - )); - } - - #[test] - fn ambiguity() { - #[derive(Resource)] - struct X; - - fn res_ref(_x: Res) {} - fn res_mut(_x: ResMut) {} - - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.set_build_settings(ScheduleBuildSettings { - ambiguity_detection: LogLevel::Error, - ..Default::default() - }); - - schedule.add_systems((res_ref, res_mut)); - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(ScheduleBuildError::Ambiguity(_)))); - } - } - - mod system_ambiguity { - #[cfg(feature = "trace")] - use alloc::collections::BTreeSet; - - use super::*; - use crate::prelude::*; - - #[derive(Resource)] - struct R; - - #[derive(Component)] - struct A; - - #[derive(Component)] - struct B; - - #[derive(Event, BufferedEvent)] - struct E; - - #[derive(Resource, Component)] - struct RC; - - fn empty_system() {} - fn res_system(_res: Res) {} - fn resmut_system(_res: ResMut) {} - fn nonsend_system(_ns: NonSend) {} - fn nonsendmut_system(_ns: NonSendMut) {} - fn read_component_system(_query: Query<&A>) {} - fn write_component_system(_query: Query<&mut A>) {} - fn with_filtered_component_system(_query: Query<&mut A, With>) {} - fn without_filtered_component_system(_query: Query<&mut A, Without>) {} - fn entity_ref_system(_query: Query) {} - fn entity_mut_system(_query: Query) {} - fn event_reader_system(_reader: EventReader) {} - fn event_writer_system(_writer: EventWriter) {} - fn event_resource_system(_events: ResMut>) {} - fn read_world_system(_world: &World) {} - fn write_world_system(_world: &mut World) {} - - // Tests for conflict detection - - #[test] - fn one_of_everything() { - let mut world = World::new(); - world.insert_resource(R); - world.spawn(A); - world.init_resource::>(); - - let mut schedule = Schedule::default(); - schedule - // nonsendmut system deliberately conflicts with resmut system - .add_systems((resmut_system, write_component_system, event_writer_system)); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 0); - } - - #[test] - fn read_only() { - let mut world = World::new(); - world.insert_resource(R); - world.spawn(A); - world.init_resource::>(); - - let mut schedule = Schedule::default(); - schedule.add_systems(( - empty_system, - empty_system, - res_system, - res_system, - nonsend_system, - nonsend_system, - read_component_system, - read_component_system, - entity_ref_system, - entity_ref_system, - event_reader_system, - event_reader_system, - read_world_system, - read_world_system, - )); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 0); - } - - #[test] - fn read_world() { - let mut world = World::new(); - world.insert_resource(R); - world.spawn(A); - world.init_resource::>(); - - let mut schedule = Schedule::default(); - schedule.add_systems(( - resmut_system, - write_component_system, - event_writer_system, - read_world_system, - )); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 3); - } - - #[test] - fn resources() { - let mut world = World::new(); - world.insert_resource(R); - - let mut schedule = Schedule::default(); - schedule.add_systems((resmut_system, res_system)); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 1); - } - - #[test] - fn nonsend() { - let mut world = World::new(); - world.insert_resource(R); - - let mut schedule = Schedule::default(); - schedule.add_systems((nonsendmut_system, nonsend_system)); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 1); - } - - #[test] - fn components() { - let mut world = World::new(); - world.spawn(A); - - let mut schedule = Schedule::default(); - schedule.add_systems((read_component_system, write_component_system)); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 1); - } - - #[test] - fn filtered_components() { - let mut world = World::new(); - world.spawn(A); - - let mut schedule = Schedule::default(); - schedule.add_systems(( - with_filtered_component_system, - without_filtered_component_system, - )); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 0); - } - - #[test] - fn events() { - let mut world = World::new(); - world.init_resource::>(); - - let mut schedule = Schedule::default(); - schedule.add_systems(( - // All of these systems clash - event_reader_system, - event_writer_system, - event_resource_system, - )); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 3); - } - - /// Test that when a struct is both a Resource and a Component, they do not - /// conflict with each other. - #[test] - fn shared_resource_mut_component() { - let mut world = World::new(); - world.insert_resource(RC); - - let mut schedule = Schedule::default(); - schedule.add_systems((|_: ResMut| {}, |_: Query<&mut RC>| {})); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 0); - } - - #[test] - fn resource_mut_and_entity_ref() { - let mut world = World::new(); - world.insert_resource(R); - - let mut schedule = Schedule::default(); - schedule.add_systems((resmut_system, entity_ref_system)); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 0); - } - - #[test] - fn resource_and_entity_mut() { - let mut world = World::new(); - world.insert_resource(R); - - let mut schedule = Schedule::default(); - schedule.add_systems((res_system, nonsend_system, entity_mut_system)); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 0); - } - - #[test] - fn write_component_and_entity_ref() { - let mut world = World::new(); - world.insert_resource(R); - - let mut schedule = Schedule::default(); - schedule.add_systems((write_component_system, entity_ref_system)); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 1); - } - - #[test] - fn read_component_and_entity_mut() { - let mut world = World::new(); - world.insert_resource(R); - - let mut schedule = Schedule::default(); - schedule.add_systems((read_component_system, entity_mut_system)); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 1); - } - - #[test] - fn exclusive() { - let mut world = World::new(); - world.insert_resource(R); - world.spawn(A); - world.init_resource::>(); - - let mut schedule = Schedule::default(); - schedule.add_systems(( - // All 3 of these conflict with each other - write_world_system, - write_world_system, - res_system, - )); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 3); - } - - // Tests for silencing and resolving ambiguities - #[test] - fn before_and_after() { - let mut world = World::new(); - world.init_resource::>(); - - let mut schedule = Schedule::default(); - schedule.add_systems(( - event_reader_system.before(event_writer_system), - event_writer_system, - event_resource_system.after(event_writer_system), - )); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 0); - } - - #[test] - fn ignore_all_ambiguities() { - let mut world = World::new(); - world.insert_resource(R); - - let mut schedule = Schedule::default(); - schedule.add_systems(( - resmut_system.ambiguous_with_all(), - res_system, - nonsend_system, - )); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 0); - } - - #[test] - fn ambiguous_with_label() { - let mut world = World::new(); - world.insert_resource(R); - - #[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)] - struct IgnoreMe; - - let mut schedule = Schedule::default(); - schedule.add_systems(( - resmut_system.ambiguous_with(IgnoreMe), - res_system.in_set(IgnoreMe), - nonsend_system.in_set(IgnoreMe), - )); - - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 0); - } - - #[test] - fn ambiguous_with_system() { - let mut world = World::new(); - - let mut schedule = Schedule::default(); - schedule.add_systems(( - write_component_system.ambiguous_with(read_component_system), - read_component_system, - )); - let _ = schedule.initialize(&mut world); - - assert_eq!(schedule.graph().conflicting_systems().len(), 0); - } - - #[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)] - struct TestSchedule; - - // Tests that the correct ambiguities were reported in the correct order. - #[test] - #[cfg(feature = "trace")] - fn correct_ambiguities() { - fn system_a(_res: ResMut) {} - fn system_b(_res: ResMut) {} - fn system_c(_res: ResMut) {} - fn system_d(_res: ResMut) {} - fn system_e(_res: ResMut) {} - - let mut world = World::new(); - world.insert_resource(R); - - let mut schedule = Schedule::new(TestSchedule); - schedule.add_systems(( - system_a, - system_b, - system_c.ambiguous_with_all(), - system_d.ambiguous_with(system_b), - system_e.after(system_a), - )); - - schedule.graph_mut().initialize(&mut world); - let _ = schedule.graph_mut().build_schedule( - &mut world, - TestSchedule.intern(), - &BTreeSet::new(), - ); - - let ambiguities: Vec<_> = schedule - .graph() - .conflicts_to_string(schedule.graph().conflicting_systems(), world.components()) - .map(|item| { - ( - item.0, - item.1, - item.2 - .into_iter() - .map(|name| name.to_string()) - .collect::>(), - ) - }) - .collect(); - - let expected = &[ - ( - "system_d".to_string(), - "system_a".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], - ), - ( - "system_d".to_string(), - "system_e".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], - ), - ( - "system_b".to_string(), - "system_a".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], - ), - ( - "system_b".to_string(), - "system_e".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], - ), - ]; - - // ordering isn't stable so do this - for entry in expected { - assert!(ambiguities.contains(entry)); - } - } - - // Test that anonymous set names work properly - // Related issue https://github.com/bevyengine/bevy/issues/9641 - #[test] - #[cfg(feature = "trace")] - fn anonymous_set_name() { - let mut schedule = Schedule::new(TestSchedule); - schedule.add_systems((resmut_system, resmut_system).run_if(|| true)); - - let mut world = World::new(); - schedule.graph_mut().initialize(&mut world); - let _ = schedule.graph_mut().build_schedule( - &mut world, - TestSchedule.intern(), - &BTreeSet::new(), - ); - - let ambiguities: Vec<_> = schedule - .graph() - .conflicts_to_string(schedule.graph().conflicting_systems(), world.components()) - .map(|item| { - ( - item.0, - item.1, - item.2 - .into_iter() - .map(|name| name.to_string()) - .collect::>(), - ) - }) - .collect(); - - assert_eq!( - ambiguities[0], - ( - "resmut_system (in set (resmut_system, resmut_system))".to_string(), - "resmut_system (in set (resmut_system, resmut_system))".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], - ) - ); - } - - #[test] - fn ignore_component_resource_ambiguities() { - let mut world = World::new(); - world.insert_resource(R); - world.allow_ambiguous_resource::(); - let mut schedule = Schedule::new(TestSchedule); - - // check resource - schedule.add_systems((resmut_system, res_system)); - schedule.initialize(&mut world).unwrap(); - assert!(schedule.graph().conflicting_systems().is_empty()); - - // check components - world.allow_ambiguous_component::(); - schedule.add_systems((write_component_system, read_component_system)); - schedule.initialize(&mut world).unwrap(); - assert!(schedule.graph().conflicting_systems().is_empty()); - } - } - - #[cfg(feature = "bevy_debug_stepping")] - mod stepping { - use super::*; - use bevy_ecs::system::SystemState; - - #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] - pub struct TestSchedule; - - macro_rules! assert_executor_supports_stepping { - ($executor:expr) => { - // create a test schedule - let mut schedule = Schedule::new(TestSchedule); - schedule - .set_executor_kind($executor) - .add_systems(|| -> () { panic!("Executor ignored Stepping") }); - - // Add our schedule to stepping & and enable stepping; this should - // prevent any systems in the schedule from running - let mut stepping = Stepping::default(); - stepping.add_schedule(TestSchedule).enable(); - - // create a world, and add the stepping resource - let mut world = World::default(); - world.insert_resource(stepping); - - // start a new frame by running ihe begin_frame() system - let mut system_state: SystemState>> = - SystemState::new(&mut world); - let res = system_state.get_mut(&mut world); - Stepping::begin_frame(res); - - // now run the schedule; this will panic if the executor doesn't - // handle stepping - schedule.run(&mut world); - }; - } - - /// verify the [`SimpleExecutor`] supports stepping - #[test] - #[expect(deprecated, reason = "We still need to test this.")] - fn simple_executor() { - assert_executor_supports_stepping!(ExecutorKind::Simple); - } - - /// verify the [`SingleThreadedExecutor`] supports stepping - #[test] - fn single_threaded_executor() { - assert_executor_supports_stepping!(ExecutorKind::SingleThreaded); - } - - /// verify the [`MultiThreadedExecutor`] supports stepping - #[test] - fn multi_threaded_executor() { - assert_executor_supports_stepping!(ExecutorKind::MultiThreaded); - } - } -} diff --git a/src/schedule/pass.rs b/src/schedule/pass.rs deleted file mode 100644 index c980378..0000000 --- a/src/schedule/pass.rs +++ /dev/null @@ -1,83 +0,0 @@ -use alloc::{boxed::Box, vec::Vec}; -use core::any::{Any, TypeId}; - -use super::{DiGraph, NodeId, ScheduleBuildError, ScheduleGraph}; -use crate::{ - schedule::{SystemKey, SystemSetKey}, - world::World, -}; -use bevy_utils::TypeIdMap; -use core::fmt::Debug; - -/// A pass for modular modification of the dependency graph. -pub trait ScheduleBuildPass: Send + Sync + Debug + 'static { - /// Custom options for dependencies between sets or systems. - type EdgeOptions: 'static; - - /// Called when a dependency between sets or systems was explicitly added to the graph. - fn add_dependency(&mut self, from: NodeId, to: NodeId, options: Option<&Self::EdgeOptions>); - - /// Called while flattening the dependency graph. For each `set`, this method is called - /// with the `systems` associated with the set as well as an immutable reference to the current graph. - /// Instead of modifying the graph directly, this method should return an iterator of edges to add to the graph. - fn collapse_set( - &mut self, - set: SystemSetKey, - systems: &[SystemKey], - dependency_flattened: &DiGraph, - ) -> impl Iterator; - - /// The implementation will be able to modify the `ScheduleGraph` here. - fn build( - &mut self, - world: &mut World, - graph: &mut ScheduleGraph, - dependency_flattened: &mut DiGraph, - ) -> Result<(), ScheduleBuildError>; -} - -/// Object safe version of [`ScheduleBuildPass`]. -pub(super) trait ScheduleBuildPassObj: Send + Sync + Debug { - fn build( - &mut self, - world: &mut World, - graph: &mut ScheduleGraph, - dependency_flattened: &mut DiGraph, - ) -> Result<(), ScheduleBuildError>; - - fn collapse_set( - &mut self, - set: SystemSetKey, - systems: &[SystemKey], - dependency_flattened: &DiGraph, - dependencies_to_add: &mut Vec<(NodeId, NodeId)>, - ); - fn add_dependency(&mut self, from: NodeId, to: NodeId, all_options: &TypeIdMap>); -} - -impl ScheduleBuildPassObj for T { - fn build( - &mut self, - world: &mut World, - graph: &mut ScheduleGraph, - dependency_flattened: &mut DiGraph, - ) -> Result<(), ScheduleBuildError> { - self.build(world, graph, dependency_flattened) - } - fn collapse_set( - &mut self, - set: SystemSetKey, - systems: &[SystemKey], - dependency_flattened: &DiGraph, - dependencies_to_add: &mut Vec<(NodeId, NodeId)>, - ) { - let iter = self.collapse_set(set, systems, dependency_flattened); - dependencies_to_add.extend(iter); - } - fn add_dependency(&mut self, from: NodeId, to: NodeId, all_options: &TypeIdMap>) { - let option = all_options - .get(&TypeId::of::()) - .and_then(|x| x.downcast_ref::()); - self.add_dependency(from, to, option); - } -} diff --git a/src/schedule/schedule.rs b/src/schedule/schedule.rs deleted file mode 100644 index 0384377..0000000 --- a/src/schedule/schedule.rs +++ /dev/null @@ -1,3018 +0,0 @@ -#![expect( - clippy::module_inception, - reason = "This instance of module inception is being discussed; see #17344." -)] -use alloc::{ - boxed::Box, - collections::{BTreeMap, BTreeSet}, - format, - string::{String, ToString}, - vec, - vec::Vec, -}; -use bevy_platform::collections::{HashMap, HashSet}; -use bevy_utils::{default, prelude::DebugName, TypeIdMap}; -use core::{ - any::{Any, TypeId}, - fmt::{Debug, Write}, -}; -use fixedbitset::FixedBitSet; -use log::{error, info, warn}; -use pass::ScheduleBuildPassObj; -use slotmap::{new_key_type, SecondaryMap, SlotMap}; -use thiserror::Error; -#[cfg(feature = "trace")] -use tracing::info_span; - -use crate::component::CheckChangeTicks; -use crate::{ - component::{ComponentId, Components}, - prelude::Component, - query::FilteredAccessSet, - resource::Resource, - schedule::*, - system::ScheduleSystem, - world::World, -}; - -use crate::{query::AccessConflicts, storage::SparseSetIndex}; -pub use stepping::Stepping; -use Direction::{Incoming, Outgoing}; - -/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s excluding the current running [`Schedule`]. -#[derive(Default, Resource)] -pub struct Schedules { - inner: HashMap, - /// List of [`ComponentId`]s to ignore when reporting system order ambiguity conflicts - pub ignored_scheduling_ambiguities: BTreeSet, -} - -impl Schedules { - /// Constructs an empty `Schedules` with zero initial capacity. - pub fn new() -> Self { - Self::default() - } - - /// Inserts a labeled schedule into the map. - /// - /// If the map already had an entry for `label`, `schedule` is inserted, - /// and the old schedule is returned. Otherwise, `None` is returned. - pub fn insert(&mut self, schedule: Schedule) -> Option { - self.inner.insert(schedule.label, schedule) - } - - /// Removes the schedule corresponding to the `label` from the map, returning it if it existed. - pub fn remove(&mut self, label: impl ScheduleLabel) -> Option { - self.inner.remove(&label.intern()) - } - - /// Removes the (schedule, label) pair corresponding to the `label` from the map, returning it if it existed. - pub fn remove_entry( - &mut self, - label: impl ScheduleLabel, - ) -> Option<(InternedScheduleLabel, Schedule)> { - self.inner.remove_entry(&label.intern()) - } - - /// Does a schedule with the provided label already exist? - pub fn contains(&self, label: impl ScheduleLabel) -> bool { - self.inner.contains_key(&label.intern()) - } - - /// Returns a reference to the schedule associated with `label`, if it exists. - pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> { - self.inner.get(&label.intern()) - } - - /// Returns a mutable reference to the schedule associated with `label`, if it exists. - pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> { - self.inner.get_mut(&label.intern()) - } - - /// Returns a mutable reference to the schedules associated with `label`, creating one if it doesn't already exist. - pub fn entry(&mut self, label: impl ScheduleLabel) -> &mut Schedule { - self.inner - .entry(label.intern()) - .or_insert_with(|| Schedule::new(label)) - } - - /// Returns an iterator over all schedules. Iteration order is undefined. - pub fn iter(&self) -> impl Iterator { - self.inner - .iter() - .map(|(label, schedule)| (&**label, schedule)) - } - /// Returns an iterator over mutable references to all schedules. Iteration order is undefined. - pub fn iter_mut(&mut self) -> impl Iterator { - self.inner - .iter_mut() - .map(|(label, schedule)| (&**label, schedule)) - } - - /// Iterates the change ticks of all systems in all stored schedules and clamps any older than - /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). - /// This prevents overflow and thus prevents false positives. - pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) { - #[cfg(feature = "trace")] - let _all_span = info_span!("check stored schedule ticks").entered(); - #[cfg_attr( - not(feature = "trace"), - expect( - unused_variables, - reason = "The `label` variable goes unused if the `trace` feature isn't active" - ) - )] - for (label, schedule) in &mut self.inner { - #[cfg(feature = "trace")] - let name = format!("{label:?}"); - #[cfg(feature = "trace")] - let _one_span = info_span!("check schedule ticks", name = &name).entered(); - schedule.check_change_ticks(check); - } - } - - /// Applies the provided [`ScheduleBuildSettings`] to all schedules. - pub fn configure_schedules(&mut self, schedule_build_settings: ScheduleBuildSettings) { - for (_, schedule) in &mut self.inner { - schedule.set_build_settings(schedule_build_settings.clone()); - } - } - - /// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`. - pub fn allow_ambiguous_component(&mut self, world: &mut World) { - self.ignored_scheduling_ambiguities - .insert(world.register_component::()); - } - - /// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`. - pub fn allow_ambiguous_resource(&mut self, world: &mut World) { - self.ignored_scheduling_ambiguities - .insert(world.components_registrator().register_resource::()); - } - - /// Iterate through the [`ComponentId`]'s that will be ignored. - pub fn iter_ignored_ambiguities(&self) -> impl Iterator + '_ { - self.ignored_scheduling_ambiguities.iter() - } - - /// Prints the names of the components and resources with [`info`] - /// - /// May panic or retrieve incorrect names if [`Components`] is not from the same - /// world - pub fn print_ignored_ambiguities(&self, components: &Components) { - let mut message = - "System order ambiguities caused by conflicts on the following types are ignored:\n" - .to_string(); - for id in self.iter_ignored_ambiguities() { - writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap(); - } - - info!("{message}"); - } - - /// Adds one or more systems to the [`Schedule`] matching the provided [`ScheduleLabel`]. - pub fn add_systems( - &mut self, - schedule: impl ScheduleLabel, - systems: impl IntoScheduleConfigs, - ) -> &mut Self { - self.entry(schedule).add_systems(systems); - - self - } - - /// Configures a collection of system sets in the provided schedule, adding any sets that do not exist. - #[track_caller] - pub fn configure_sets( - &mut self, - schedule: impl ScheduleLabel, - sets: impl IntoScheduleConfigs, - ) -> &mut Self { - self.entry(schedule).configure_sets(sets); - - self - } - - /// Suppress warnings and errors that would result from systems in these sets having ambiguities - /// (conflicting access but indeterminate order) with systems in `set`. - /// - /// When possible, do this directly in the `.add_systems(Update, a.ambiguous_with(b))` call. - /// However, sometimes two independent plugins `A` and `B` are reported as ambiguous, which you - /// can only suppress as the consumer of both. - #[track_caller] - pub fn ignore_ambiguity( - &mut self, - schedule: impl ScheduleLabel, - a: S1, - b: S2, - ) -> &mut Self - where - S1: IntoSystemSet, - S2: IntoSystemSet, - { - self.entry(schedule).ignore_ambiguity(a, b); - - self - } -} - -fn make_executor(kind: ExecutorKind) -> Box { - match kind { - #[expect(deprecated, reason = "We still need to support this.")] - ExecutorKind::Simple => Box::new(SimpleExecutor::new()), - ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()), - #[cfg(feature = "std")] - ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()), - } -} - -/// Chain systems into dependencies -#[derive(Default)] -pub enum Chain { - /// Systems are independent. Nodes are allowed to run in any order. - #[default] - Unchained, - /// Systems are chained. `before -> after` ordering constraints - /// will be added between the successive elements. - Chained(TypeIdMap>), -} - -impl Chain { - /// Specify that the systems must be chained. - pub fn set_chained(&mut self) { - if matches!(self, Chain::Unchained) { - *self = Self::Chained(Default::default()); - }; - } - /// Specify that the systems must be chained, and add the specified configuration for - /// all dependencies created between these systems. - pub fn set_chained_with_config(&mut self, config: T) { - self.set_chained(); - if let Chain::Chained(config_map) = self { - config_map.insert(TypeId::of::(), Box::new(config)); - } else { - unreachable!() - }; - } -} - -/// A collection of systems, and the metadata and executor needed to run them -/// in a certain order under certain conditions. -/// -/// # Schedule labels -/// -/// Each schedule has a [`ScheduleLabel`] value. This value is used to uniquely identify the -/// schedule when added to a [`World`]’s [`Schedules`], and may be used to specify which schedule -/// a system should be added to. -/// -/// # Example -/// -/// Here is an example of a `Schedule` running a "Hello world" system: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// fn hello_world() { println!("Hello world!") } -/// -/// fn main() { -/// let mut world = World::new(); -/// let mut schedule = Schedule::default(); -/// schedule.add_systems(hello_world); -/// -/// schedule.run(&mut world); -/// } -/// ``` -/// -/// A schedule can also run several systems in an ordered way: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// fn system_one() { println!("System 1 works!") } -/// fn system_two() { println!("System 2 works!") } -/// fn system_three() { println!("System 3 works!") } -/// -/// fn main() { -/// let mut world = World::new(); -/// let mut schedule = Schedule::default(); -/// schedule.add_systems(( -/// system_two, -/// system_one.before(system_two), -/// system_three.after(system_two), -/// )); -/// -/// schedule.run(&mut world); -/// } -/// ``` -/// -/// Schedules are often inserted into a [`World`] and identified by their [`ScheduleLabel`] only: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// use bevy_ecs::schedule::ScheduleLabel; -/// -/// // Declare a new schedule label. -/// #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] -/// struct Update; -/// -/// // This system shall be part of the schedule. -/// fn an_update_system() { -/// println!("Hello world!"); -/// } -/// -/// fn main() { -/// let mut world = World::new(); -/// -/// // Add a system to the schedule with that label (creating it automatically). -/// world.get_resource_or_init::().add_systems(Update, an_update_system); -/// -/// // Run the schedule, and therefore run the system. -/// world.run_schedule(Update); -/// } -/// ``` -pub struct Schedule { - label: InternedScheduleLabel, - graph: ScheduleGraph, - executable: SystemSchedule, - executor: Box, - executor_initialized: bool, -} - -#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)] -struct DefaultSchedule; - -impl Default for Schedule { - /// Creates a schedule with a default label. Only use in situations where - /// you don't care about the [`ScheduleLabel`]. Inserting a default schedule - /// into the world risks overwriting another schedule. For most situations - /// you should use [`Schedule::new`]. - fn default() -> Self { - Self::new(DefaultSchedule) - } -} - -impl Schedule { - /// Constructs an empty `Schedule`. - pub fn new(label: impl ScheduleLabel) -> Self { - let mut this = Self { - label: label.intern(), - graph: ScheduleGraph::new(), - executable: SystemSchedule::new(), - executor: make_executor(ExecutorKind::default()), - executor_initialized: false, - }; - // Call `set_build_settings` to add any default build passes - this.set_build_settings(Default::default()); - this - } - - /// Returns the [`InternedScheduleLabel`] for this `Schedule`, - /// corresponding to the [`ScheduleLabel`] this schedule was created with. - pub fn label(&self) -> InternedScheduleLabel { - self.label - } - - /// Add a collection of systems to the schedule. - pub fn add_systems( - &mut self, - systems: impl IntoScheduleConfigs, - ) -> &mut Self { - self.graph.process_configs(systems.into_configs(), false); - self - } - - /// Suppress warnings and errors that would result from systems in these sets having ambiguities - /// (conflicting access but indeterminate order) with systems in `set`. - #[track_caller] - pub fn ignore_ambiguity(&mut self, a: S1, b: S2) -> &mut Self - where - S1: IntoSystemSet, - S2: IntoSystemSet, - { - let a = a.into_system_set(); - let b = b.into_system_set(); - - let Some(&a_id) = self.graph.system_set_ids.get(&a.intern()) else { - panic!( - "Could not mark system as ambiguous, `{:?}` was not found in the schedule. - Did you try to call `ambiguous_with` before adding the system to the world?", - a - ); - }; - let Some(&b_id) = self.graph.system_set_ids.get(&b.intern()) else { - panic!( - "Could not mark system as ambiguous, `{:?}` was not found in the schedule. - Did you try to call `ambiguous_with` before adding the system to the world?", - b - ); - }; - - self.graph - .ambiguous_with - .add_edge(NodeId::Set(a_id), NodeId::Set(b_id)); - - self - } - - /// Configures a collection of system sets in this schedule, adding them if they does not exist. - #[track_caller] - pub fn configure_sets( - &mut self, - sets: impl IntoScheduleConfigs, - ) -> &mut Self { - self.graph.configure_sets(sets); - self - } - - /// Add a custom build pass to the schedule. - pub fn add_build_pass(&mut self, pass: T) -> &mut Self { - self.graph.passes.insert(TypeId::of::(), Box::new(pass)); - self - } - - /// Remove a custom build pass. - pub fn remove_build_pass(&mut self) { - self.graph.passes.remove(&TypeId::of::()); - } - - /// Changes miscellaneous build settings. - /// - /// If [`settings.auto_insert_apply_deferred`][ScheduleBuildSettings::auto_insert_apply_deferred] - /// is `false`, this clears `*_ignore_deferred` edge settings configured so far. - /// - /// Generally this method should be used before adding systems or set configurations to the schedule, - /// not after. - pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self { - if settings.auto_insert_apply_deferred { - if !self - .graph - .passes - .contains_key(&TypeId::of::()) - { - self.add_build_pass(passes::AutoInsertApplyDeferredPass::default()); - } - } else { - self.remove_build_pass::(); - } - self.graph.settings = settings; - self - } - - /// Returns the schedule's current `ScheduleBuildSettings`. - pub fn get_build_settings(&self) -> ScheduleBuildSettings { - self.graph.settings.clone() - } - - /// Returns the schedule's current execution strategy. - pub fn get_executor_kind(&self) -> ExecutorKind { - self.executor.kind() - } - - /// Sets the schedule's execution strategy. - pub fn set_executor_kind(&mut self, executor: ExecutorKind) -> &mut Self { - if executor != self.executor.kind() { - self.executor = make_executor(executor); - self.executor_initialized = false; - } - self - } - - /// Set whether the schedule applies deferred system buffers on final time or not. This is a catch-all - /// in case a system uses commands but was not explicitly ordered before an instance of - /// [`ApplyDeferred`]. By default this - /// setting is true, but may be disabled if needed. - pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self { - self.executor.set_apply_final_deferred(apply_final_deferred); - self - } - - /// Runs all systems in this schedule on the `world`, using its current execution strategy. - pub fn run(&mut self, world: &mut World) { - #[cfg(feature = "trace")] - let _span = info_span!("schedule", name = ?self.label).entered(); - - world.check_change_ticks(); - self.initialize(world) - .unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label)); - - let error_handler = world.default_error_handler(); - - #[cfg(not(feature = "bevy_debug_stepping"))] - self.executor - .run(&mut self.executable, world, None, error_handler); - - #[cfg(feature = "bevy_debug_stepping")] - { - let skip_systems = match world.get_resource_mut::() { - None => None, - Some(mut stepping) => stepping.skipped_systems(self), - }; - - self.executor.run( - &mut self.executable, - world, - skip_systems.as_ref(), - error_handler, - ); - } - } - - /// Initializes any newly-added systems and conditions, rebuilds the executable schedule, - /// and re-initializes the executor. - /// - /// Moves all systems and run conditions out of the [`ScheduleGraph`]. - pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> { - if self.graph.changed { - self.graph.initialize(world); - let ignored_ambiguities = world - .get_resource_or_init::() - .ignored_scheduling_ambiguities - .clone(); - self.graph.update_schedule( - world, - &mut self.executable, - &ignored_ambiguities, - self.label, - )?; - self.graph.changed = false; - self.executor_initialized = false; - } - - if !self.executor_initialized { - self.executor.init(&self.executable); - self.executor_initialized = true; - } - - Ok(()) - } - - /// Returns the [`ScheduleGraph`]. - pub fn graph(&self) -> &ScheduleGraph { - &self.graph - } - - /// Returns a mutable reference to the [`ScheduleGraph`]. - pub fn graph_mut(&mut self) -> &mut ScheduleGraph { - &mut self.graph - } - - /// Returns the [`SystemSchedule`]. - pub(crate) fn executable(&self) -> &SystemSchedule { - &self.executable - } - - /// Iterates the change ticks of all systems in the schedule and clamps any older than - /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). - /// This prevents overflow and thus prevents false positives. - pub fn check_change_ticks(&mut self, check: CheckChangeTicks) { - for SystemWithAccess { system, .. } in &mut self.executable.systems { - if !is_apply_deferred(system) { - system.check_change_tick(check); - } - } - - for conditions in &mut self.executable.system_conditions { - for system in conditions { - system.condition.check_change_tick(check); - } - } - - for conditions in &mut self.executable.set_conditions { - for system in conditions { - system.condition.check_change_tick(check); - } - } - } - - /// Directly applies any accumulated [`Deferred`](crate::system::Deferred) system parameters (like [`Commands`](crate::prelude::Commands)) to the `world`. - /// - /// Like always, deferred system parameters are applied in the "topological sort order" of the schedule graph. - /// As a result, buffers from one system are only guaranteed to be applied before those of other systems - /// if there is an explicit system ordering between the two systems. - /// - /// This is used in rendering to extract data from the main world, storing the data in system buffers, - /// before applying their buffers in a different world. - pub fn apply_deferred(&mut self, world: &mut World) { - for SystemWithAccess { system, .. } in &mut self.executable.systems { - system.apply_deferred(world); - } - } - - /// Returns an iterator over all systems in this schedule. - /// - /// Note: this method will return [`ScheduleNotInitialized`] if the - /// schedule has never been initialized or run. - pub fn systems( - &self, - ) -> Result + Sized, ScheduleNotInitialized> - { - if !self.executor_initialized { - return Err(ScheduleNotInitialized); - } - - let iter = self - .executable - .system_ids - .iter() - .zip(&self.executable.systems) - .map(|(&node_id, system)| (node_id, &system.system)); - - Ok(iter) - } - - /// Returns the number of systems in this schedule. - pub fn systems_len(&self) -> usize { - if !self.executor_initialized { - self.graph.systems.len() - } else { - self.executable.systems.len() - } - } -} - -/// A directed acyclic graph structure. -#[derive(Default)] -pub struct Dag { - /// A directed graph. - graph: DiGraph, - /// A cached topological ordering of the graph. - topsort: Vec, -} - -impl Dag { - fn new() -> Self { - Self { - graph: DiGraph::default(), - topsort: Vec::new(), - } - } - - /// The directed graph of the stored systems, connected by their ordering dependencies. - pub fn graph(&self) -> &DiGraph { - &self.graph - } - - /// A cached topological ordering of the graph. - /// - /// The order is determined by the ordering dependencies between systems. - pub fn cached_topsort(&self) -> &[NodeId] { - &self.topsort - } -} - -/// A [`SystemSet`] with metadata, stored in a [`ScheduleGraph`]. -struct SystemSetNode { - inner: InternedSystemSet, -} - -impl SystemSetNode { - pub fn new(set: InternedSystemSet) -> Self { - Self { inner: set } - } - - pub fn name(&self) -> String { - format!("{:?}", &self.inner) - } - - pub fn is_system_type(&self) -> bool { - self.inner.system_type().is_some() - } - - pub fn is_anonymous(&self) -> bool { - self.inner.is_anonymous() - } -} - -/// A [`SystemWithAccess`] stored in a [`ScheduleGraph`]. -pub struct SystemNode { - inner: Option, -} - -/// A [`ScheduleSystem`] stored alongside the access returned from [`System::initialize`](crate::system::System::initialize). -pub struct SystemWithAccess { - /// The system itself. - pub system: ScheduleSystem, - /// The access returned by [`System::initialize`](crate::system::System::initialize). - /// This will be empty if the system has not been initialized yet. - pub access: FilteredAccessSet, -} - -impl SystemWithAccess { - /// Constructs a new [`SystemWithAccess`] from a [`ScheduleSystem`]. - /// The `access` will initially be empty. - pub fn new(system: ScheduleSystem) -> Self { - Self { - system, - access: FilteredAccessSet::new(), - } - } -} - -/// A [`BoxedCondition`] stored alongside the access returned from [`System::initialize`](crate::system::System::initialize). -pub struct ConditionWithAccess { - /// The condition itself. - pub condition: BoxedCondition, - /// The access returned by [`System::initialize`](crate::system::System::initialize). - /// This will be empty if the system has not been initialized yet. - pub access: FilteredAccessSet, -} - -impl ConditionWithAccess { - /// Constructs a new [`ConditionWithAccess`] from a [`BoxedCondition`]. - /// The `access` will initially be empty. - pub const fn new(condition: BoxedCondition) -> Self { - Self { - condition, - access: FilteredAccessSet::new(), - } - } -} - -impl SystemNode { - /// Create a new [`SystemNode`] - pub fn new(system: ScheduleSystem) -> Self { - Self { - inner: Some(SystemWithAccess::new(system)), - } - } - - /// Obtain a reference to the [`SystemWithAccess`] represented by this node. - pub fn get(&self) -> Option<&SystemWithAccess> { - self.inner.as_ref() - } - - /// Obtain a mutable reference to the [`SystemWithAccess`] represented by this node. - pub fn get_mut(&mut self) -> Option<&mut SystemWithAccess> { - self.inner.as_mut() - } -} - -new_key_type! { - /// A unique identifier for a system in a [`ScheduleGraph`]. - pub struct SystemKey; - /// A unique identifier for a system set in a [`ScheduleGraph`]. - pub struct SystemSetKey; -} - -enum UninitializedId { - System(SystemKey), - Set { - key: SystemSetKey, - first_uninit_condition: usize, - }, -} - -/// Metadata for a [`Schedule`]. -/// -/// The order isn't optimized; calling `ScheduleGraph::build_schedule` will return a -/// `SystemSchedule` where the order is optimized for execution. -#[derive(Default)] -pub struct ScheduleGraph { - /// List of systems in the schedule - pub systems: SlotMap, - /// List of conditions for each system, in the same order as `systems` - pub system_conditions: SecondaryMap>, - /// List of system sets in the schedule - system_sets: SlotMap, - /// List of conditions for each system set, in the same order as `system_sets` - system_set_conditions: SecondaryMap>, - /// Map from system set to node id - system_set_ids: HashMap, - /// Systems that have not been initialized yet; for system sets, we store the index of the first uninitialized condition - /// (all the conditions after that index still need to be initialized) - uninit: Vec, - /// Directed acyclic graph of the hierarchy (which systems/sets are children of which sets) - hierarchy: Dag, - /// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets) - dependency: Dag, - ambiguous_with: UnGraph, - /// Nodes that are allowed to have ambiguous ordering relationship with any other systems. - pub ambiguous_with_all: HashSet, - conflicting_systems: Vec<(SystemKey, SystemKey, Vec)>, - anonymous_sets: usize, - changed: bool, - settings: ScheduleBuildSettings, - - passes: BTreeMap>, -} - -impl ScheduleGraph { - /// Creates an empty [`ScheduleGraph`] with default settings. - pub fn new() -> Self { - Self { - systems: SlotMap::with_key(), - system_conditions: SecondaryMap::new(), - system_sets: SlotMap::with_key(), - system_set_conditions: SecondaryMap::new(), - system_set_ids: HashMap::default(), - uninit: Vec::new(), - hierarchy: Dag::new(), - dependency: Dag::new(), - ambiguous_with: UnGraph::default(), - ambiguous_with_all: HashSet::default(), - conflicting_systems: Vec::new(), - anonymous_sets: 0, - changed: false, - settings: default(), - passes: default(), - } - } - - /// Returns the system at the given [`SystemKey`], if it exists. - pub fn get_system_at(&self, key: SystemKey) -> Option<&ScheduleSystem> { - self.systems - .get(key) - .and_then(|system| system.get()) - .map(|system| &system.system) - } - - /// Returns `true` if the given system set is part of the graph. Otherwise, returns `false`. - pub fn contains_set(&self, set: impl SystemSet) -> bool { - self.system_set_ids.contains_key(&set.intern()) - } - - /// Returns the system at the given [`NodeId`]. - /// - /// Panics if it doesn't exist. - #[track_caller] - pub fn system_at(&self, key: SystemKey) -> &ScheduleSystem { - self.get_system_at(key) - .unwrap_or_else(|| panic!("system with key {key:?} does not exist in this Schedule")) - } - - /// Returns the set at the given [`NodeId`], if it exists. - pub fn get_set_at(&self, key: SystemSetKey) -> Option<&dyn SystemSet> { - self.system_sets.get(key).map(|set| &*set.inner) - } - - /// Returns the set at the given [`NodeId`]. - /// - /// Panics if it doesn't exist. - #[track_caller] - pub fn set_at(&self, id: SystemSetKey) -> &dyn SystemSet { - self.get_set_at(id) - .unwrap_or_else(|| panic!("set with id {id:?} does not exist in this Schedule")) - } - - /// Returns the conditions for the set at the given [`SystemSetKey`], if it exists. - pub fn get_set_conditions_at(&self, key: SystemSetKey) -> Option<&[ConditionWithAccess]> { - self.system_set_conditions.get(key).map(Vec::as_slice) - } - - /// Returns the conditions for the set at the given [`SystemSetKey`]. - /// - /// Panics if it doesn't exist. - #[track_caller] - pub fn set_conditions_at(&self, key: SystemSetKey) -> &[ConditionWithAccess] { - self.get_set_conditions_at(key) - .unwrap_or_else(|| panic!("set with key {key:?} does not exist in this Schedule")) - } - - /// Returns an iterator over all systems in this schedule, along with the conditions for each system. - pub fn systems( - &self, - ) -> impl Iterator { - self.systems.iter().filter_map(|(key, system_node)| { - let system = &system_node.inner.as_ref()?.system; - let conditions = self.system_conditions.get(key)?; - Some((key, system, conditions.as_slice())) - }) - } - - /// Returns an iterator over all system sets in this schedule, along with the conditions for each - /// system set. - pub fn system_sets( - &self, - ) -> impl Iterator { - self.system_sets.iter().filter_map(|(key, set_node)| { - let set = &*set_node.inner; - let conditions = self.system_set_conditions.get(key)?.as_slice(); - Some((key, set, conditions)) - }) - } - - /// Returns the [`Dag`] of the hierarchy. - /// - /// The hierarchy is a directed acyclic graph of the systems and sets, - /// where an edge denotes that a system or set is the child of another set. - pub fn hierarchy(&self) -> &Dag { - &self.hierarchy - } - - /// Returns the [`Dag`] of the dependencies in the schedule. - /// - /// Nodes in this graph are systems and sets, and edges denote that - /// a system or set has to run before another system or set. - pub fn dependency(&self) -> &Dag { - &self.dependency - } - - /// Returns the list of systems that conflict with each other, i.e. have ambiguities in their access. - /// - /// If the `Vec` is empty, the systems conflict on [`World`] access. - /// Must be called after [`ScheduleGraph::build_schedule`] to be non-empty. - pub fn conflicting_systems(&self) -> &[(SystemKey, SystemKey, Vec)] { - &self.conflicting_systems - } - - fn process_config( - &mut self, - config: ScheduleConfig, - collect_nodes: bool, - ) -> ProcessConfigsResult { - ProcessConfigsResult { - densely_chained: true, - nodes: collect_nodes - .then_some(T::process_config(self, config)) - .into_iter() - .collect(), - } - } - - fn apply_collective_conditions< - T: ProcessScheduleConfig + Schedulable, - >( - &mut self, - configs: &mut [ScheduleConfigs], - collective_conditions: Vec, - ) { - if !collective_conditions.is_empty() { - if let [config] = configs { - for condition in collective_conditions { - config.run_if_dyn(condition); - } - } else { - let set = self.create_anonymous_set(); - for config in configs.iter_mut() { - config.in_set_inner(set.intern()); - } - let mut set_config = InternedSystemSet::into_config(set.intern()); - set_config.conditions.extend(collective_conditions); - self.configure_set_inner(set_config).unwrap(); - } - } - } - - /// Adds the config nodes to the graph. - /// - /// `collect_nodes` controls whether the `NodeId`s of the processed config nodes are stored in the returned [`ProcessConfigsResult`]. - /// `process_config` is the function which processes each individual config node and returns a corresponding `NodeId`. - /// - /// The fields on the returned [`ProcessConfigsResult`] are: - /// - `nodes`: a vector of all node ids contained in the nested `ScheduleConfigs` - /// - `densely_chained`: a boolean that is true if all nested nodes are linearly chained (with successive `after` orderings) in the order they are defined - #[track_caller] - fn process_configs< - T: ProcessScheduleConfig + Schedulable, - >( - &mut self, - configs: ScheduleConfigs, - collect_nodes: bool, - ) -> ProcessConfigsResult { - match configs { - ScheduleConfigs::ScheduleConfig(config) => self.process_config(config, collect_nodes), - ScheduleConfigs::Configs { - metadata, - mut configs, - collective_conditions, - } => { - self.apply_collective_conditions(&mut configs, collective_conditions); - - let is_chained = matches!(metadata, Chain::Chained(_)); - - // Densely chained if - // * chained and all configs in the chain are densely chained, or - // * unchained with a single densely chained config - let mut densely_chained = is_chained || configs.len() == 1; - let mut configs = configs.into_iter(); - let mut nodes = Vec::new(); - - let Some(first) = configs.next() else { - return ProcessConfigsResult { - nodes: Vec::new(), - densely_chained, - }; - }; - let mut previous_result = self.process_configs(first, collect_nodes || is_chained); - densely_chained &= previous_result.densely_chained; - - for current in configs { - let current_result = self.process_configs(current, collect_nodes || is_chained); - densely_chained &= current_result.densely_chained; - - if let Chain::Chained(chain_options) = &metadata { - // if the current result is densely chained, we only need to chain the first node - let current_nodes = if current_result.densely_chained { - ¤t_result.nodes[..1] - } else { - ¤t_result.nodes - }; - // if the previous result was densely chained, we only need to chain the last node - let previous_nodes = if previous_result.densely_chained { - &previous_result.nodes[previous_result.nodes.len() - 1..] - } else { - &previous_result.nodes - }; - - for previous_node in previous_nodes { - for current_node in current_nodes { - self.dependency - .graph - .add_edge(*previous_node, *current_node); - - for pass in self.passes.values_mut() { - pass.add_dependency( - *previous_node, - *current_node, - chain_options, - ); - } - } - } - } - if collect_nodes { - nodes.append(&mut previous_result.nodes); - } - - previous_result = current_result; - } - if collect_nodes { - nodes.append(&mut previous_result.nodes); - } - - ProcessConfigsResult { - nodes, - densely_chained, - } - } - } - } - - /// Add a [`ScheduleConfig`] to the graph, including its dependencies and conditions. - fn add_system_inner( - &mut self, - config: ScheduleConfig, - ) -> Result { - let key = self.systems.insert(SystemNode::new(config.node)); - self.system_conditions.insert( - key, - config - .conditions - .into_iter() - .map(ConditionWithAccess::new) - .collect(), - ); - // system init has to be deferred (need `&mut World`) - self.uninit.push(UninitializedId::System(key)); - - // graph updates are immediate - self.update_graphs(NodeId::System(key), config.metadata)?; - - Ok(NodeId::System(key)) - } - - #[track_caller] - fn configure_sets(&mut self, sets: impl IntoScheduleConfigs) { - self.process_configs(sets.into_configs(), false); - } - - /// Add a single `ScheduleConfig` to the graph, including its dependencies and conditions. - fn configure_set_inner( - &mut self, - set: ScheduleConfig, - ) -> Result { - let ScheduleConfig { - node: set, - metadata, - conditions, - } = set; - - let key = match self.system_set_ids.get(&set) { - Some(&id) => id, - None => self.add_set(set), - }; - - // graph updates are immediate - self.update_graphs(NodeId::Set(key), metadata)?; - - // system init has to be deferred (need `&mut World`) - let system_set_conditions = self.system_set_conditions.entry(key).unwrap().or_default(); - self.uninit.push(UninitializedId::Set { - key, - first_uninit_condition: system_set_conditions.len(), - }); - system_set_conditions.extend(conditions.into_iter().map(ConditionWithAccess::new)); - - Ok(NodeId::Set(key)) - } - - fn add_set(&mut self, set: InternedSystemSet) -> SystemSetKey { - let key = self.system_sets.insert(SystemSetNode::new(set)); - self.system_set_conditions.insert(key, Vec::new()); - self.system_set_ids.insert(set, key); - key - } - - fn create_anonymous_set(&mut self) -> AnonymousSet { - let id = self.anonymous_sets; - self.anonymous_sets += 1; - AnonymousSet::new(id) - } - - /// Check that no set is included in itself. - /// Add all the sets from the [`GraphInfo`]'s hierarchy to the graph. - fn check_hierarchy_sets( - &mut self, - id: NodeId, - graph_info: &GraphInfo, - ) -> Result<(), ScheduleBuildError> { - for &set in &graph_info.hierarchy { - if let Some(&set_id) = self.system_set_ids.get(&set) { - if let NodeId::Set(key) = id - && set_id == key - { - { - return Err(ScheduleBuildError::HierarchyLoop( - self.get_node_name(&NodeId::Set(key)), - )); - } - } - } else { - // If the set is not in the graph, we add it - self.add_set(set); - } - } - - Ok(()) - } - - /// Checks that no system set is dependent on itself. - /// Add all the sets from the [`GraphInfo`]'s dependencies to the graph. - fn check_edges( - &mut self, - id: NodeId, - graph_info: &GraphInfo, - ) -> Result<(), ScheduleBuildError> { - for Dependency { set, .. } in &graph_info.dependencies { - if let Some(&set_id) = self.system_set_ids.get(set) { - if let NodeId::Set(key) = id - && set_id == key - { - return Err(ScheduleBuildError::DependencyLoop( - self.get_node_name(&NodeId::Set(key)), - )); - } - } else { - // If the set is not in the graph, we add it - self.add_set(*set); - } - } - - Ok(()) - } - - /// Add all the sets from the [`GraphInfo`]'s ambiguity to the graph. - fn add_ambiguities(&mut self, graph_info: &GraphInfo) { - if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with { - for set in ambiguous_with { - if !self.system_set_ids.contains_key(set) { - self.add_set(*set); - } - } - } - } - - /// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`] - fn update_graphs( - &mut self, - id: NodeId, - graph_info: GraphInfo, - ) -> Result<(), ScheduleBuildError> { - self.check_hierarchy_sets(id, &graph_info)?; - self.check_edges(id, &graph_info)?; - self.add_ambiguities(&graph_info); - self.changed = true; - - let GraphInfo { - hierarchy: sets, - dependencies, - ambiguous_with, - .. - } = graph_info; - - self.hierarchy.graph.add_node(id); - self.dependency.graph.add_node(id); - - for key in sets.into_iter().map(|set| self.system_set_ids[&set]) { - self.hierarchy.graph.add_edge(NodeId::Set(key), id); - - // ensure set also appears in dependency graph - self.dependency.graph.add_node(NodeId::Set(key)); - } - - for (kind, key, options) in dependencies - .into_iter() - .map(|Dependency { kind, set, options }| (kind, self.system_set_ids[&set], options)) - { - let (lhs, rhs) = match kind { - DependencyKind::Before => (id, NodeId::Set(key)), - DependencyKind::After => (NodeId::Set(key), id), - }; - self.dependency.graph.add_edge(lhs, rhs); - for pass in self.passes.values_mut() { - pass.add_dependency(lhs, rhs, &options); - } - - // ensure set also appears in hierarchy graph - self.hierarchy.graph.add_node(NodeId::Set(key)); - } - - match ambiguous_with { - Ambiguity::Check => (), - Ambiguity::IgnoreWithSet(ambiguous_with) => { - for key in ambiguous_with - .into_iter() - .map(|set| self.system_set_ids[&set]) - { - self.ambiguous_with.add_edge(id, NodeId::Set(key)); - } - } - Ambiguity::IgnoreAll => { - self.ambiguous_with_all.insert(id); - } - } - - Ok(()) - } - - /// Initializes any newly-added systems and conditions by calling [`System::initialize`](crate::system::System) - pub fn initialize(&mut self, world: &mut World) { - for id in self.uninit.drain(..) { - match id { - UninitializedId::System(key) => { - let system = self.systems[key].get_mut().unwrap(); - system.access = system.system.initialize(world); - for condition in &mut self.system_conditions[key] { - condition.access = condition.condition.initialize(world); - } - } - UninitializedId::Set { - key, - first_uninit_condition, - } => { - for condition in self.system_set_conditions[key] - .iter_mut() - .skip(first_uninit_condition) - { - condition.access = condition.condition.initialize(world); - } - } - } - } - } - - /// Build a [`SystemSchedule`] optimized for scheduler access from the [`ScheduleGraph`]. - /// - /// This method also - /// - checks for dependency or hierarchy cycles - /// - checks for system access conflicts and reports ambiguities - pub fn build_schedule( - &mut self, - world: &mut World, - schedule_label: InternedScheduleLabel, - ignored_ambiguities: &BTreeSet, - ) -> Result { - // check hierarchy for cycles - self.hierarchy.topsort = - self.topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy)?; - - let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort); - self.optionally_check_hierarchy_conflicts(&hier_results.transitive_edges, schedule_label)?; - - // remove redundant edges - self.hierarchy.graph = hier_results.transitive_reduction; - - // check dependencies for cycles - self.dependency.topsort = - self.topsort_graph(&self.dependency.graph, ReportCycles::Dependency)?; - - // check for systems or system sets depending on sets they belong to - let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort); - self.check_for_cross_dependencies(&dep_results, &hier_results.connected)?; - - // map all system sets to their systems - // go in reverse topological order (bottom-up) for efficiency - let (set_systems, set_system_bitsets) = - self.map_sets_to_systems(&self.hierarchy.topsort, &self.hierarchy.graph); - self.check_order_but_intersect(&dep_results.connected, &set_system_bitsets)?; - - // check that there are no edges to system-type sets that have multiple instances - self.check_system_type_set_ambiguity(&set_systems)?; - - let mut dependency_flattened = self.get_dependency_flattened(&set_systems); - - // modify graph with build passes - let mut passes = core::mem::take(&mut self.passes); - for pass in passes.values_mut() { - pass.build(world, self, &mut dependency_flattened)?; - } - self.passes = passes; - - // topsort - let mut dependency_flattened_dag = Dag { - topsort: self.topsort_graph(&dependency_flattened, ReportCycles::Dependency)?, - graph: dependency_flattened, - }; - - let flat_results = check_graph( - &dependency_flattened_dag.graph, - &dependency_flattened_dag.topsort, - ); - - // remove redundant edges - dependency_flattened_dag.graph = flat_results.transitive_reduction; - - // flatten: combine `in_set` with `ambiguous_with` information - let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems); - - // check for conflicts - let conflicting_systems = self.get_conflicting_systems( - &flat_results.disconnected, - &ambiguous_with_flattened, - ignored_ambiguities, - ); - self.optionally_check_conflicts(&conflicting_systems, world.components(), schedule_label)?; - self.conflicting_systems = conflicting_systems; - - // build the schedule - Ok(self.build_schedule_inner(dependency_flattened_dag, hier_results.reachable)) - } - - /// Return a map from system set `NodeId` to a list of system `NodeId`s that are included in the set. - /// Also return a map from system set `NodeId` to a `FixedBitSet` of system `NodeId`s that are included in the set, - /// where the bitset order is the same as `self.systems` - fn map_sets_to_systems( - &self, - hierarchy_topsort: &[NodeId], - hierarchy_graph: &DiGraph, - ) -> ( - HashMap>, - HashMap>, - ) { - let mut set_systems: HashMap> = - HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default()); - let mut set_system_sets: HashMap> = - HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default()); - for &id in hierarchy_topsort.iter().rev() { - let NodeId::Set(set_key) = id else { - continue; - }; - - let mut systems = Vec::new(); - let mut system_set = HashSet::with_capacity(self.systems.len()); - - for child in hierarchy_graph.neighbors_directed(id, Outgoing) { - match child { - NodeId::System(key) => { - systems.push(key); - system_set.insert(key); - } - NodeId::Set(key) => { - let child_systems = set_systems.get(&key).unwrap(); - let child_system_set = set_system_sets.get(&key).unwrap(); - systems.extend_from_slice(child_systems); - system_set.extend(child_system_set.iter()); - } - } - } - - set_systems.insert(set_key, systems); - set_system_sets.insert(set_key, system_set); - } - (set_systems, set_system_sets) - } - - fn get_dependency_flattened( - &mut self, - set_systems: &HashMap>, - ) -> DiGraph { - // flatten: combine `in_set` with `before` and `after` information - // have to do it like this to preserve transitivity - let mut dependency_flattened = self.dependency.graph.clone(); - let mut temp = Vec::new(); - for (&set, systems) in set_systems { - for pass in self.passes.values_mut() { - pass.collapse_set(set, systems, &dependency_flattened, &mut temp); - } - if systems.is_empty() { - // collapse dependencies for empty sets - for a in dependency_flattened.neighbors_directed(NodeId::Set(set), Incoming) { - for b in dependency_flattened.neighbors_directed(NodeId::Set(set), Outgoing) { - temp.push((a, b)); - } - } - } else { - for a in dependency_flattened.neighbors_directed(NodeId::Set(set), Incoming) { - for &sys in systems { - temp.push((a, NodeId::System(sys))); - } - } - - for b in dependency_flattened.neighbors_directed(NodeId::Set(set), Outgoing) { - for &sys in systems { - temp.push((NodeId::System(sys), b)); - } - } - } - - dependency_flattened.remove_node(NodeId::Set(set)); - for (a, b) in temp.drain(..) { - dependency_flattened.add_edge(a, b); - } - } - - dependency_flattened - } - - fn get_ambiguous_with_flattened( - &self, - set_systems: &HashMap>, - ) -> UnGraph { - let mut ambiguous_with_flattened = UnGraph::default(); - for (lhs, rhs) in self.ambiguous_with.all_edges() { - match (lhs, rhs) { - (NodeId::System(_), NodeId::System(_)) => { - ambiguous_with_flattened.add_edge(lhs, rhs); - } - (NodeId::Set(lhs), NodeId::System(_)) => { - for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) { - ambiguous_with_flattened.add_edge(NodeId::System(lhs_), rhs); - } - } - (NodeId::System(_), NodeId::Set(rhs)) => { - for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) { - ambiguous_with_flattened.add_edge(lhs, NodeId::System(rhs_)); - } - } - (NodeId::Set(lhs), NodeId::Set(rhs)) => { - for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) { - for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) { - ambiguous_with_flattened - .add_edge(NodeId::System(lhs_), NodeId::System(rhs_)); - } - } - } - } - } - - ambiguous_with_flattened - } - - fn get_conflicting_systems( - &self, - flat_results_disconnected: &Vec<(NodeId, NodeId)>, - ambiguous_with_flattened: &UnGraph, - ignored_ambiguities: &BTreeSet, - ) -> Vec<(SystemKey, SystemKey, Vec)> { - let mut conflicting_systems = Vec::new(); - for &(a, b) in flat_results_disconnected { - if ambiguous_with_flattened.contains_edge(a, b) - || self.ambiguous_with_all.contains(&a) - || self.ambiguous_with_all.contains(&b) - { - continue; - } - - let NodeId::System(a) = a else { - panic!( - "Encountered a non-system node in the flattened disconnected results: {a:?}" - ); - }; - let NodeId::System(b) = b else { - panic!( - "Encountered a non-system node in the flattened disconnected results: {b:?}" - ); - }; - let system_a = self.systems[a].get().unwrap(); - let system_b = self.systems[b].get().unwrap(); - if system_a.system.is_exclusive() || system_b.system.is_exclusive() { - conflicting_systems.push((a, b, Vec::new())); - } else { - let access_a = &system_a.access; - let access_b = &system_b.access; - if !access_a.is_compatible(access_b) { - match access_a.get_conflicts(access_b) { - AccessConflicts::Individual(conflicts) => { - let conflicts: Vec<_> = conflicts - .ones() - .map(ComponentId::get_sparse_set_index) - .filter(|id| !ignored_ambiguities.contains(id)) - .collect(); - if !conflicts.is_empty() { - conflicting_systems.push((a, b, conflicts)); - } - } - AccessConflicts::All => { - // there is no specific component conflicting, but the systems are overall incompatible - // for example 2 systems with `Query` - conflicting_systems.push((a, b, Vec::new())); - } - } - } - } - } - - conflicting_systems - } - - fn build_schedule_inner( - &self, - dependency_flattened_dag: Dag, - hier_results_reachable: FixedBitSet, - ) -> SystemSchedule { - let dg_system_ids = dependency_flattened_dag - .topsort - .iter() - .filter_map(NodeId::as_system) - .collect::>(); - let dg_system_idx_map = dg_system_ids - .iter() - .cloned() - .enumerate() - .map(|(i, id)| (id, i)) - .collect::>(); - - let hg_systems = self - .hierarchy - .topsort - .iter() - .cloned() - .enumerate() - .filter_map(|(i, id)| Some((i, id.as_system()?))) - .collect::>(); - - let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self - .hierarchy - .topsort - .iter() - .cloned() - .enumerate() - .filter_map(|(i, id)| { - // ignore system sets that have no conditions - // ignore system type sets (already covered, they don't have conditions) - let key = id.as_set()?; - (!self.system_set_conditions[key].is_empty()).then_some((i, key)) - }) - .unzip(); - - let sys_count = self.systems.len(); - let set_with_conditions_count = hg_set_ids.len(); - let hg_node_count = self.hierarchy.graph.node_count(); - - // get the number of dependencies and the immediate dependents of each system - // (needed by multi_threaded executor to run systems in the correct order) - let mut system_dependencies = Vec::with_capacity(sys_count); - let mut system_dependents = Vec::with_capacity(sys_count); - for &sys_key in &dg_system_ids { - let num_dependencies = dependency_flattened_dag - .graph - .neighbors_directed(NodeId::System(sys_key), Incoming) - .count(); - - let dependents = dependency_flattened_dag - .graph - .neighbors_directed(NodeId::System(sys_key), Outgoing) - .filter_map(|dep_id| { - let dep_key = dep_id.as_system()?; - Some(dg_system_idx_map[&dep_key]) - }) - .collect::>(); - - system_dependencies.push(num_dependencies); - system_dependents.push(dependents); - } - - // get the rows and columns of the hierarchy graph's reachability matrix - // (needed to we can evaluate conditions in the correct order) - let mut systems_in_sets_with_conditions = - vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count]; - for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() { - let bitset = &mut systems_in_sets_with_conditions[i]; - for &(col, sys_key) in &hg_systems { - let idx = dg_system_idx_map[&sys_key]; - let is_descendant = hier_results_reachable[index(row, col, hg_node_count)]; - bitset.set(idx, is_descendant); - } - } - - let mut sets_with_conditions_of_systems = - vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count]; - for &(col, sys_key) in &hg_systems { - let i = dg_system_idx_map[&sys_key]; - let bitset = &mut sets_with_conditions_of_systems[i]; - for (idx, &row) in hg_set_with_conditions_idxs - .iter() - .enumerate() - .take_while(|&(_idx, &row)| row < col) - { - let is_ancestor = hier_results_reachable[index(row, col, hg_node_count)]; - bitset.set(idx, is_ancestor); - } - } - - SystemSchedule { - systems: Vec::with_capacity(sys_count), - system_conditions: Vec::with_capacity(sys_count), - set_conditions: Vec::with_capacity(set_with_conditions_count), - system_ids: dg_system_ids, - set_ids: hg_set_ids, - system_dependencies, - system_dependents, - sets_with_conditions_of_systems, - systems_in_sets_with_conditions, - } - } - - /// Updates the `SystemSchedule` from the `ScheduleGraph`. - fn update_schedule( - &mut self, - world: &mut World, - schedule: &mut SystemSchedule, - ignored_ambiguities: &BTreeSet, - schedule_label: InternedScheduleLabel, - ) -> Result<(), ScheduleBuildError> { - if !self.uninit.is_empty() { - return Err(ScheduleBuildError::Uninitialized); - } - - // move systems out of old schedule - for ((key, system), conditions) in schedule - .system_ids - .drain(..) - .zip(schedule.systems.drain(..)) - .zip(schedule.system_conditions.drain(..)) - { - self.systems[key].inner = Some(system); - self.system_conditions[key] = conditions; - } - - for (key, conditions) in schedule - .set_ids - .drain(..) - .zip(schedule.set_conditions.drain(..)) - { - self.system_set_conditions[key] = conditions; - } - - *schedule = self.build_schedule(world, schedule_label, ignored_ambiguities)?; - - // move systems into new schedule - for &key in &schedule.system_ids { - let system = self.systems[key].inner.take().unwrap(); - let conditions = core::mem::take(&mut self.system_conditions[key]); - schedule.systems.push(system); - schedule.system_conditions.push(conditions); - } - - for &key in &schedule.set_ids { - let conditions = core::mem::take(&mut self.system_set_conditions[key]); - schedule.set_conditions.push(conditions); - } - - Ok(()) - } -} - -/// Values returned by [`ScheduleGraph::process_configs`] -struct ProcessConfigsResult { - /// All nodes contained inside this `process_configs` call's [`ScheduleConfigs`] hierarchy, - /// if `ancestor_chained` is true - nodes: Vec, - /// True if and only if all nodes are "densely chained", meaning that all nested nodes - /// are linearly chained (as if `after` system ordering had been applied between each node) - /// in the order they are defined - densely_chained: bool, -} - -/// Trait used by [`ScheduleGraph::process_configs`] to process a single [`ScheduleConfig`]. -trait ProcessScheduleConfig: Schedulable + Sized { - /// Process a single [`ScheduleConfig`]. - fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig) -> NodeId; -} - -impl ProcessScheduleConfig for ScheduleSystem { - fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig) -> NodeId { - schedule_graph.add_system_inner(config).unwrap() - } -} - -impl ProcessScheduleConfig for InternedSystemSet { - fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig) -> NodeId { - schedule_graph.configure_set_inner(config).unwrap() - } -} - -/// Used to select the appropriate reporting function. -pub enum ReportCycles { - /// When sets contain themselves - Hierarchy, - /// When the graph is no longer a DAG - Dependency, -} - -// methods for reporting errors -impl ScheduleGraph { - fn get_node_name(&self, id: &NodeId) -> String { - self.get_node_name_inner(id, self.settings.report_sets) - } - - #[inline] - fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String { - match *id { - NodeId::System(key) => { - let name = self.systems[key].get().unwrap().system.name(); - let name = if self.settings.use_shortnames { - name.shortname().to_string() - } else { - name.to_string() - }; - if report_sets { - let sets = self.names_of_sets_containing_node(id); - if sets.is_empty() { - name - } else if sets.len() == 1 { - format!("{name} (in set {})", sets[0]) - } else { - format!("{name} (in sets {})", sets.join(", ")) - } - } else { - name - } - } - NodeId::Set(key) => { - let set = &self.system_sets[key]; - if set.is_anonymous() { - self.anonymous_set_name(id) - } else { - set.name() - } - } - } - } - - fn anonymous_set_name(&self, id: &NodeId) -> String { - format!( - "({})", - self.hierarchy - .graph - .edges_directed(*id, Outgoing) - // never get the sets of the members or this will infinite recurse when the report_sets setting is on. - .map(|(_, member_id)| self.get_node_name_inner(&member_id, false)) - .reduce(|a, b| format!("{a}, {b}")) - .unwrap_or_default() - ) - } - - fn get_node_kind(&self, id: &NodeId) -> &'static str { - match id { - NodeId::System(_) => "system", - NodeId::Set(_) => "system set", - } - } - - /// If [`ScheduleBuildSettings::hierarchy_detection`] is [`LogLevel::Ignore`] this check - /// is skipped. - fn optionally_check_hierarchy_conflicts( - &self, - transitive_edges: &[(NodeId, NodeId)], - schedule_label: InternedScheduleLabel, - ) -> Result<(), ScheduleBuildError> { - if self.settings.hierarchy_detection == LogLevel::Ignore || transitive_edges.is_empty() { - return Ok(()); - } - - let message = self.get_hierarchy_conflicts_error_message(transitive_edges); - match self.settings.hierarchy_detection { - LogLevel::Ignore => unreachable!(), - LogLevel::Warn => { - error!("Schedule {schedule_label:?} has redundant edges:\n {message}"); - Ok(()) - } - LogLevel::Error => Err(ScheduleBuildError::HierarchyRedundancy(message)), - } - } - - fn get_hierarchy_conflicts_error_message( - &self, - transitive_edges: &[(NodeId, NodeId)], - ) -> String { - let mut message = String::from("hierarchy contains redundant edge(s)"); - for (parent, child) in transitive_edges { - writeln!( - message, - " -- {} `{}` cannot be child of set `{}`, longer path exists", - self.get_node_kind(child), - self.get_node_name(child), - self.get_node_name(parent), - ) - .unwrap(); - } - - message - } - - /// Tries to topologically sort `graph`. - /// - /// If the graph is acyclic, returns [`Ok`] with the list of [`NodeId`] in a valid - /// topological order. If the graph contains cycles, returns [`Err`] with the list of - /// strongly-connected components that contain cycles (also in a valid topological order). - /// - /// # Errors - /// - /// If the graph contain cycles, then an error is returned. - pub fn topsort_graph( - &self, - graph: &DiGraph, - report: ReportCycles, - ) -> Result, ScheduleBuildError> { - // Tarjan's SCC algorithm returns elements in *reverse* topological order. - let mut top_sorted_nodes = Vec::with_capacity(graph.node_count()); - let mut sccs_with_cycles = Vec::new(); - - for scc in graph.iter_sccs() { - // A strongly-connected component is a group of nodes who can all reach each other - // through one or more paths. If an SCC contains more than one node, there must be - // at least one cycle within them. - top_sorted_nodes.extend_from_slice(&scc); - if scc.len() > 1 { - sccs_with_cycles.push(scc); - } - } - - if sccs_with_cycles.is_empty() { - // reverse to get topological order - top_sorted_nodes.reverse(); - Ok(top_sorted_nodes) - } else { - let mut cycles = Vec::new(); - for scc in &sccs_with_cycles { - cycles.append(&mut simple_cycles_in_component(graph, scc)); - } - - let error = match report { - ReportCycles::Hierarchy => ScheduleBuildError::HierarchyCycle( - self.get_hierarchy_cycles_error_message(&cycles), - ), - ReportCycles::Dependency => ScheduleBuildError::DependencyCycle( - self.get_dependency_cycles_error_message(&cycles), - ), - }; - - Err(error) - } - } - - /// Logs details of cycles in the hierarchy graph. - fn get_hierarchy_cycles_error_message(&self, cycles: &[Vec]) -> String { - let mut message = format!("schedule has {} in_set cycle(s):\n", cycles.len()); - for (i, cycle) in cycles.iter().enumerate() { - let mut names = cycle.iter().map(|id| self.get_node_name(id)); - let first_name = names.next().unwrap(); - writeln!( - message, - "cycle {}: set `{first_name}` contains itself", - i + 1, - ) - .unwrap(); - writeln!(message, "set `{first_name}`").unwrap(); - for name in names.chain(core::iter::once(first_name)) { - writeln!(message, " ... which contains set `{name}`").unwrap(); - } - writeln!(message).unwrap(); - } - - message - } - - /// Logs details of cycles in the dependency graph. - fn get_dependency_cycles_error_message(&self, cycles: &[Vec]) -> String { - let mut message = format!("schedule has {} before/after cycle(s):\n", cycles.len()); - for (i, cycle) in cycles.iter().enumerate() { - let mut names = cycle - .iter() - .map(|id| (self.get_node_kind(id), self.get_node_name(id))); - let (first_kind, first_name) = names.next().unwrap(); - writeln!( - message, - "cycle {}: {first_kind} `{first_name}` must run before itself", - i + 1, - ) - .unwrap(); - writeln!(message, "{first_kind} `{first_name}`").unwrap(); - for (kind, name) in names.chain(core::iter::once((first_kind, first_name))) { - writeln!(message, " ... which must run before {kind} `{name}`").unwrap(); - } - writeln!(message).unwrap(); - } - - message - } - - fn check_for_cross_dependencies( - &self, - dep_results: &CheckGraphResults, - hier_results_connected: &HashSet<(NodeId, NodeId)>, - ) -> Result<(), ScheduleBuildError> { - for &(a, b) in &dep_results.connected { - if hier_results_connected.contains(&(a, b)) || hier_results_connected.contains(&(b, a)) - { - let name_a = self.get_node_name(&a); - let name_b = self.get_node_name(&b); - return Err(ScheduleBuildError::CrossDependency(name_a, name_b)); - } - } - - Ok(()) - } - - fn check_order_but_intersect( - &self, - dep_results_connected: &HashSet<(NodeId, NodeId)>, - set_system_sets: &HashMap>, - ) -> Result<(), ScheduleBuildError> { - // check that there is no ordering between system sets that intersect - for (a, b) in dep_results_connected { - let (NodeId::Set(a_key), NodeId::Set(b_key)) = (a, b) else { - continue; - }; - - let a_systems = set_system_sets.get(a_key).unwrap(); - let b_systems = set_system_sets.get(b_key).unwrap(); - - if !a_systems.is_disjoint(b_systems) { - return Err(ScheduleBuildError::SetsHaveOrderButIntersect( - self.get_node_name(a), - self.get_node_name(b), - )); - } - } - - Ok(()) - } - - fn check_system_type_set_ambiguity( - &self, - set_systems: &HashMap>, - ) -> Result<(), ScheduleBuildError> { - for (&key, systems) in set_systems { - let set = &self.system_sets[key]; - if set.is_system_type() { - let instances = systems.len(); - let ambiguous_with = self.ambiguous_with.edges(NodeId::Set(key)); - let before = self - .dependency - .graph - .edges_directed(NodeId::Set(key), Incoming); - let after = self - .dependency - .graph - .edges_directed(NodeId::Set(key), Outgoing); - let relations = before.count() + after.count() + ambiguous_with.count(); - if instances > 1 && relations > 0 { - return Err(ScheduleBuildError::SystemTypeSetAmbiguity( - self.get_node_name(&NodeId::Set(key)), - )); - } - } - } - Ok(()) - } - - /// if [`ScheduleBuildSettings::ambiguity_detection`] is [`LogLevel::Ignore`], this check is skipped - fn optionally_check_conflicts( - &self, - conflicts: &[(SystemKey, SystemKey, Vec)], - components: &Components, - schedule_label: InternedScheduleLabel, - ) -> Result<(), ScheduleBuildError> { - if self.settings.ambiguity_detection == LogLevel::Ignore || conflicts.is_empty() { - return Ok(()); - } - - let message = self.get_conflicts_error_message(conflicts, components); - match self.settings.ambiguity_detection { - LogLevel::Ignore => Ok(()), - LogLevel::Warn => { - warn!("Schedule {schedule_label:?} has ambiguities.\n{message}"); - Ok(()) - } - LogLevel::Error => Err(ScheduleBuildError::Ambiguity(message)), - } - } - - fn get_conflicts_error_message( - &self, - ambiguities: &[(SystemKey, SystemKey, Vec)], - components: &Components, - ) -> String { - let n_ambiguities = ambiguities.len(); - - let mut message = format!( - "{n_ambiguities} pairs of systems with conflicting data access have indeterminate execution order. \ - Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n", - ); - - for (name_a, name_b, conflicts) in self.conflicts_to_string(ambiguities, components) { - writeln!(message, " -- {name_a} and {name_b}").unwrap(); - - if !conflicts.is_empty() { - writeln!(message, " conflict on: {conflicts:?}").unwrap(); - } else { - // one or both systems must be exclusive - let world = core::any::type_name::(); - writeln!(message, " conflict on: {world}").unwrap(); - } - } - - message - } - - /// convert conflicts to human readable format - pub fn conflicts_to_string<'a>( - &'a self, - ambiguities: &'a [(SystemKey, SystemKey, Vec)], - components: &'a Components, - ) -> impl Iterator)> + 'a { - ambiguities - .iter() - .map(move |(system_a, system_b, conflicts)| { - let name_a = self.get_node_name(&NodeId::System(*system_a)); - let name_b = self.get_node_name(&NodeId::System(*system_b)); - - let conflict_names: Vec<_> = conflicts - .iter() - .map(|id| components.get_name(*id).unwrap()) - .collect(); - - (name_a, name_b, conflict_names) - }) - } - - fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(SystemSetKey) -> bool) { - for (set_id, _) in self.hierarchy.graph.edges_directed(id, Incoming) { - let NodeId::Set(set_key) = set_id else { - continue; - }; - if f(set_key) { - self.traverse_sets_containing_node(NodeId::Set(set_key), f); - } - } - } - - fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec { - let mut sets = >::default(); - self.traverse_sets_containing_node(*id, &mut |key| { - !self.system_sets[key].is_system_type() && sets.insert(key) - }); - let mut sets: Vec<_> = sets - .into_iter() - .map(|key| self.get_node_name(&NodeId::Set(key))) - .collect(); - sets.sort(); - sets - } -} - -/// Category of errors encountered during schedule construction. -#[derive(Error, Debug)] -#[non_exhaustive] -pub enum ScheduleBuildError { - /// A system set contains itself. - #[error("System set `{0}` contains itself.")] - HierarchyLoop(String), - /// The hierarchy of system sets contains a cycle. - #[error("System set hierarchy contains cycle(s).\n{0}")] - HierarchyCycle(String), - /// The hierarchy of system sets contains redundant edges. - /// - /// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`]. - #[error("System set hierarchy contains redundant edges.\n{0}")] - HierarchyRedundancy(String), - /// A system (set) has been told to run before itself. - #[error("System set `{0}` depends on itself.")] - DependencyLoop(String), - /// The dependency graph contains a cycle. - #[error("System dependencies contain cycle(s).\n{0}")] - DependencyCycle(String), - /// Tried to order a system (set) relative to a system set it belongs to. - #[error("`{0}` and `{1}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")] - CrossDependency(String, String), - /// Tried to order system sets that share systems. - #[error("`{0}` and `{1}` have a `before`-`after` relationship (which may be transitive) but share systems.")] - SetsHaveOrderButIntersect(String, String), - /// Tried to order a system (set) relative to all instances of some system function. - #[error("Tried to order against `{0}` in a schedule that has more than one `{0}` instance. `{0}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")] - SystemTypeSetAmbiguity(String), - /// Systems with conflicting access have indeterminate run order. - /// - /// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`]. - #[error("Systems with conflicting access have indeterminate run order.\n{0}")] - Ambiguity(String), - /// Tried to run a schedule before all of its systems have been initialized. - #[error("Systems in schedule have not been initialized.")] - Uninitialized, -} - -/// Specifies how schedule construction should respond to detecting a certain kind of issue. -#[derive(Debug, Clone, PartialEq)] -pub enum LogLevel { - /// Occurrences are completely ignored. - Ignore, - /// Occurrences are logged only. - Warn, - /// Occurrences are logged and result in errors. - Error, -} - -/// Specifies miscellaneous settings for schedule construction. -#[derive(Clone, Debug)] -pub struct ScheduleBuildSettings { - /// Determines whether the presence of ambiguities (systems with conflicting access but indeterminate order) - /// is only logged or also results in an [`Ambiguity`](ScheduleBuildError::Ambiguity) error. - /// - /// Defaults to [`LogLevel::Ignore`]. - pub ambiguity_detection: LogLevel, - /// Determines whether the presence of redundant edges in the hierarchy of system sets is only - /// logged or also results in a [`HierarchyRedundancy`](ScheduleBuildError::HierarchyRedundancy) - /// error. - /// - /// Defaults to [`LogLevel::Warn`]. - pub hierarchy_detection: LogLevel, - /// Auto insert [`ApplyDeferred`] systems into the schedule, - /// when there are [`Deferred`](crate::prelude::Deferred) - /// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one - /// such deferred buffer. - /// - /// You may want to disable this if you only want to sync deferred params at the end of the schedule, - /// or want to manually insert all your sync points. - /// - /// Defaults to `true` - pub auto_insert_apply_deferred: bool, - /// If set to true, node names will be shortened instead of the fully qualified type path. - /// - /// Defaults to `true`. - pub use_shortnames: bool, - /// If set to true, report all system sets the conflicting systems are part of. - /// - /// Defaults to `true`. - pub report_sets: bool, -} - -impl Default for ScheduleBuildSettings { - fn default() -> Self { - Self::new() - } -} - -impl ScheduleBuildSettings { - /// Default build settings. - /// See the field-level documentation for the default value of each field. - pub const fn new() -> Self { - Self { - ambiguity_detection: LogLevel::Ignore, - hierarchy_detection: LogLevel::Warn, - auto_insert_apply_deferred: true, - use_shortnames: true, - report_sets: true, - } - } -} - -/// Error to denote that [`Schedule::initialize`] or [`Schedule::run`] has not yet been called for -/// this schedule. -#[derive(Error, Debug)] -#[error("executable schedule has not been built")] -pub struct ScheduleNotInitialized; - -#[cfg(test)] -mod tests { - use bevy_ecs_macros::ScheduleLabel; - - use crate::{ - error::{ignore, panic, DefaultErrorHandler, Result}, - prelude::{ApplyDeferred, Res, Resource}, - schedule::{ - tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, SystemSet, - }, - system::Commands, - world::World, - }; - - use super::Schedules; - - #[derive(Resource)] - struct Resource1; - - #[derive(Resource)] - struct Resource2; - - #[test] - fn unchanged_auto_insert_apply_deferred_has_no_effect() { - use alloc::{vec, vec::Vec}; - - #[derive(PartialEq, Debug)] - enum Entry { - System(usize), - SyncPoint(usize), - } - - #[derive(Resource, Default)] - struct Log(Vec); - - fn system(mut res: ResMut, mut commands: Commands) { - res.0.push(Entry::System(N)); - commands - .queue(|world: &mut World| world.resource_mut::().0.push(Entry::SyncPoint(N))); - } - - let mut world = World::default(); - world.init_resource::(); - let mut schedule = Schedule::default(); - schedule.add_systems((system::<1>, system::<2>).chain_ignore_deferred()); - schedule.set_build_settings(ScheduleBuildSettings { - auto_insert_apply_deferred: true, - ..Default::default() - }); - schedule.run(&mut world); - let actual = world.remove_resource::().unwrap().0; - - let expected = vec![ - Entry::System(1), - Entry::System(2), - Entry::SyncPoint(1), - Entry::SyncPoint(2), - ]; - - assert_eq!(actual, expected); - } - - // regression test for https://github.com/bevyengine/bevy/issues/9114 - #[test] - fn ambiguous_with_not_breaking_run_conditions() { - #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] - struct Set; - - let mut world = World::new(); - let mut schedule = Schedule::default(); - - let system: fn() = || { - panic!("This system must not run"); - }; - - schedule.configure_sets(Set.run_if(|| false)); - schedule.add_systems(system.ambiguous_with(|| ()).in_set(Set)); - schedule.run(&mut world); - } - - #[test] - fn inserts_a_sync_point() { - let mut schedule = Schedule::default(); - let mut world = World::default(); - schedule.add_systems( - ( - |mut commands: Commands| commands.insert_resource(Resource1), - |_: Res| {}, - ) - .chain(), - ); - schedule.run(&mut world); - - // inserted a sync point - assert_eq!(schedule.executable.systems.len(), 3); - } - - #[test] - fn explicit_sync_point_used_as_auto_sync_point() { - let mut schedule = Schedule::default(); - let mut world = World::default(); - schedule.add_systems( - ( - |mut commands: Commands| commands.insert_resource(Resource1), - |_: Res| {}, - ) - .chain(), - ); - schedule.add_systems((|| {}, ApplyDeferred, || {}).chain()); - schedule.run(&mut world); - - // No sync point was inserted, since we can reuse the explicit sync point. - assert_eq!(schedule.executable.systems.len(), 5); - } - - #[test] - fn conditional_explicit_sync_point_not_used_as_auto_sync_point() { - let mut schedule = Schedule::default(); - let mut world = World::default(); - schedule.add_systems( - ( - |mut commands: Commands| commands.insert_resource(Resource1), - |_: Res| {}, - ) - .chain(), - ); - schedule.add_systems((|| {}, ApplyDeferred.run_if(|| false), || {}).chain()); - schedule.run(&mut world); - - // A sync point was inserted, since the explicit sync point is not always run. - assert_eq!(schedule.executable.systems.len(), 6); - } - - #[test] - fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_chain() { - let mut schedule = Schedule::default(); - let mut world = World::default(); - schedule.add_systems( - ( - |mut commands: Commands| commands.insert_resource(Resource1), - |_: Res| {}, - ) - .chain(), - ); - schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().run_if(|| false)); - schedule.run(&mut world); - - // A sync point was inserted, since the explicit sync point is not always run. - assert_eq!(schedule.executable.systems.len(), 6); - } - - #[test] - fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_system_set() { - #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] - struct Set; - - let mut schedule = Schedule::default(); - let mut world = World::default(); - schedule.configure_sets(Set.run_if(|| false)); - schedule.add_systems( - ( - |mut commands: Commands| commands.insert_resource(Resource1), - |_: Res| {}, - ) - .chain(), - ); - schedule.add_systems((|| {}, ApplyDeferred.in_set(Set), || {}).chain()); - schedule.run(&mut world); - - // A sync point was inserted, since the explicit sync point is not always run. - assert_eq!(schedule.executable.systems.len(), 6); - } - - #[test] - fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_nested_system_set() - { - #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] - struct Set1; - #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] - struct Set2; - - let mut schedule = Schedule::default(); - let mut world = World::default(); - schedule.configure_sets(Set2.run_if(|| false)); - schedule.configure_sets(Set1.in_set(Set2)); - schedule.add_systems( - ( - |mut commands: Commands| commands.insert_resource(Resource1), - |_: Res| {}, - ) - .chain(), - ); - schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().in_set(Set1)); - schedule.run(&mut world); - - // A sync point was inserted, since the explicit sync point is not always run. - assert_eq!(schedule.executable.systems.len(), 6); - } - - #[test] - fn merges_sync_points_into_one() { - let mut schedule = Schedule::default(); - let mut world = World::default(); - // insert two parallel command systems, it should only create one sync point - schedule.add_systems( - ( - ( - |mut commands: Commands| commands.insert_resource(Resource1), - |mut commands: Commands| commands.insert_resource(Resource2), - ), - |_: Res, _: Res| {}, - ) - .chain(), - ); - schedule.run(&mut world); - - // inserted sync points - assert_eq!(schedule.executable.systems.len(), 4); - - // merges sync points on rebuild - schedule.add_systems((( - ( - |mut commands: Commands| commands.insert_resource(Resource1), - |mut commands: Commands| commands.insert_resource(Resource2), - ), - |_: Res, _: Res| {}, - ) - .chain(),)); - schedule.run(&mut world); - - assert_eq!(schedule.executable.systems.len(), 7); - } - - #[test] - fn adds_multiple_consecutive_syncs() { - let mut schedule = Schedule::default(); - let mut world = World::default(); - // insert two consecutive command systems, it should create two sync points - schedule.add_systems( - ( - |mut commands: Commands| commands.insert_resource(Resource1), - |mut commands: Commands| commands.insert_resource(Resource2), - |_: Res, _: Res| {}, - ) - .chain(), - ); - schedule.run(&mut world); - - assert_eq!(schedule.executable.systems.len(), 5); - } - - #[test] - fn do_not_consider_ignore_deferred_before_exclusive_system() { - let mut schedule = Schedule::default(); - let mut world = World::default(); - // chain_ignore_deferred adds no sync points usually but an exception is made for exclusive systems - schedule.add_systems( - ( - |_: Commands| {}, - // <- no sync point is added here because the following system is not exclusive - |mut commands: Commands| commands.insert_resource(Resource1), - // <- sync point is added here because the following system is exclusive which expects to see all commands to that point - |world: &mut World| assert!(world.contains_resource::()), - // <- no sync point is added here because the previous system has no deferred parameters - |_: &mut World| {}, - // <- no sync point is added here because the following system is not exclusive - |_: Commands| {}, - ) - .chain_ignore_deferred(), - ); - schedule.run(&mut world); - - assert_eq!(schedule.executable.systems.len(), 6); // 5 systems + 1 sync point - } - - #[test] - fn bubble_sync_point_through_ignore_deferred_node() { - let mut schedule = Schedule::default(); - let mut world = World::default(); - - let insert_resource_config = ( - // the first system has deferred commands - |mut commands: Commands| commands.insert_resource(Resource1), - // the second system has no deferred commands - || {}, - ) - // the first two systems are chained without a sync point in between - .chain_ignore_deferred(); - - schedule.add_systems( - ( - insert_resource_config, - // the third system would panic if the command of the first system was not applied - |_: Res| {}, - ) - // the third system is chained after the first two, possibly with a sync point in between - .chain(), - ); - - // To add a sync point between the second and third system despite the second having no commands, - // the first system has to signal the second system that there are unapplied commands. - // With that the second system will add a sync point after it so the third system will find the resource. - - schedule.run(&mut world); - - assert_eq!(schedule.executable.systems.len(), 4); // 3 systems + 1 sync point - } - - #[test] - fn disable_auto_sync_points() { - let mut schedule = Schedule::default(); - schedule.set_build_settings(ScheduleBuildSettings { - auto_insert_apply_deferred: false, - ..Default::default() - }); - let mut world = World::default(); - schedule.add_systems( - ( - |mut commands: Commands| commands.insert_resource(Resource1), - |res: Option>| assert!(res.is_none()), - ) - .chain(), - ); - schedule.run(&mut world); - - assert_eq!(schedule.executable.systems.len(), 2); - } - - mod no_sync_edges { - use super::*; - - fn insert_resource(mut commands: Commands) { - commands.insert_resource(Resource1); - } - - fn resource_does_not_exist(res: Option>) { - assert!(res.is_none()); - } - - #[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)] - enum Sets { - A, - B, - } - - fn check_no_sync_edges(add_systems: impl FnOnce(&mut Schedule)) { - let mut schedule = Schedule::default(); - let mut world = World::default(); - add_systems(&mut schedule); - - schedule.run(&mut world); - - assert_eq!(schedule.executable.systems.len(), 2); - } - - #[test] - fn system_to_system_after() { - check_no_sync_edges(|schedule| { - schedule.add_systems(( - insert_resource, - resource_does_not_exist.after_ignore_deferred(insert_resource), - )); - }); - } - - #[test] - fn system_to_system_before() { - check_no_sync_edges(|schedule| { - schedule.add_systems(( - insert_resource.before_ignore_deferred(resource_does_not_exist), - resource_does_not_exist, - )); - }); - } - - #[test] - fn set_to_system_after() { - check_no_sync_edges(|schedule| { - schedule - .add_systems((insert_resource, resource_does_not_exist.in_set(Sets::A))) - .configure_sets(Sets::A.after_ignore_deferred(insert_resource)); - }); - } - - #[test] - fn set_to_system_before() { - check_no_sync_edges(|schedule| { - schedule - .add_systems((insert_resource.in_set(Sets::A), resource_does_not_exist)) - .configure_sets(Sets::A.before_ignore_deferred(resource_does_not_exist)); - }); - } - - #[test] - fn set_to_set_after() { - check_no_sync_edges(|schedule| { - schedule - .add_systems(( - insert_resource.in_set(Sets::A), - resource_does_not_exist.in_set(Sets::B), - )) - .configure_sets(Sets::B.after_ignore_deferred(Sets::A)); - }); - } - - #[test] - fn set_to_set_before() { - check_no_sync_edges(|schedule| { - schedule - .add_systems(( - insert_resource.in_set(Sets::A), - resource_does_not_exist.in_set(Sets::B), - )) - .configure_sets(Sets::A.before_ignore_deferred(Sets::B)); - }); - } - } - - mod no_sync_chain { - use super::*; - - #[derive(Resource)] - struct Ra; - - #[derive(Resource)] - struct Rb; - - #[derive(Resource)] - struct Rc; - - fn run_schedule(expected_num_systems: usize, add_systems: impl FnOnce(&mut Schedule)) { - let mut schedule = Schedule::default(); - let mut world = World::default(); - add_systems(&mut schedule); - - schedule.run(&mut world); - - assert_eq!(schedule.executable.systems.len(), expected_num_systems); - } - - #[test] - fn only_chain_outside() { - run_schedule(5, |schedule: &mut Schedule| { - schedule.add_systems( - ( - ( - |mut commands: Commands| commands.insert_resource(Ra), - |mut commands: Commands| commands.insert_resource(Rb), - ), - ( - |res_a: Option>, res_b: Option>| { - assert!(res_a.is_some()); - assert!(res_b.is_some()); - }, - |res_a: Option>, res_b: Option>| { - assert!(res_a.is_some()); - assert!(res_b.is_some()); - }, - ), - ) - .chain(), - ); - }); - - run_schedule(4, |schedule: &mut Schedule| { - schedule.add_systems( - ( - ( - |mut commands: Commands| commands.insert_resource(Ra), - |mut commands: Commands| commands.insert_resource(Rb), - ), - ( - |res_a: Option>, res_b: Option>| { - assert!(res_a.is_none()); - assert!(res_b.is_none()); - }, - |res_a: Option>, res_b: Option>| { - assert!(res_a.is_none()); - assert!(res_b.is_none()); - }, - ), - ) - .chain_ignore_deferred(), - ); - }); - } - - #[test] - fn chain_first() { - run_schedule(6, |schedule: &mut Schedule| { - schedule.add_systems( - ( - ( - |mut commands: Commands| commands.insert_resource(Ra), - |mut commands: Commands, res_a: Option>| { - commands.insert_resource(Rb); - assert!(res_a.is_some()); - }, - ) - .chain(), - ( - |res_a: Option>, res_b: Option>| { - assert!(res_a.is_some()); - assert!(res_b.is_some()); - }, - |res_a: Option>, res_b: Option>| { - assert!(res_a.is_some()); - assert!(res_b.is_some()); - }, - ), - ) - .chain(), - ); - }); - - run_schedule(5, |schedule: &mut Schedule| { - schedule.add_systems( - ( - ( - |mut commands: Commands| commands.insert_resource(Ra), - |mut commands: Commands, res_a: Option>| { - commands.insert_resource(Rb); - assert!(res_a.is_some()); - }, - ) - .chain(), - ( - |res_a: Option>, res_b: Option>| { - assert!(res_a.is_some()); - assert!(res_b.is_none()); - }, - |res_a: Option>, res_b: Option>| { - assert!(res_a.is_some()); - assert!(res_b.is_none()); - }, - ), - ) - .chain_ignore_deferred(), - ); - }); - } - - #[test] - fn chain_second() { - run_schedule(6, |schedule: &mut Schedule| { - schedule.add_systems( - ( - ( - |mut commands: Commands| commands.insert_resource(Ra), - |mut commands: Commands| commands.insert_resource(Rb), - ), - ( - |mut commands: Commands, - res_a: Option>, - res_b: Option>| { - commands.insert_resource(Rc); - assert!(res_a.is_some()); - assert!(res_b.is_some()); - }, - |res_a: Option>, - res_b: Option>, - res_c: Option>| { - assert!(res_a.is_some()); - assert!(res_b.is_some()); - assert!(res_c.is_some()); - }, - ) - .chain(), - ) - .chain(), - ); - }); - - run_schedule(5, |schedule: &mut Schedule| { - schedule.add_systems( - ( - ( - |mut commands: Commands| commands.insert_resource(Ra), - |mut commands: Commands| commands.insert_resource(Rb), - ), - ( - |mut commands: Commands, - res_a: Option>, - res_b: Option>| { - commands.insert_resource(Rc); - assert!(res_a.is_none()); - assert!(res_b.is_none()); - }, - |res_a: Option>, - res_b: Option>, - res_c: Option>| { - assert!(res_a.is_some()); - assert!(res_b.is_some()); - assert!(res_c.is_some()); - }, - ) - .chain(), - ) - .chain_ignore_deferred(), - ); - }); - } - - #[test] - fn chain_all() { - run_schedule(7, |schedule: &mut Schedule| { - schedule.add_systems( - ( - ( - |mut commands: Commands| commands.insert_resource(Ra), - |mut commands: Commands, res_a: Option>| { - commands.insert_resource(Rb); - assert!(res_a.is_some()); - }, - ) - .chain(), - ( - |mut commands: Commands, - res_a: Option>, - res_b: Option>| { - commands.insert_resource(Rc); - assert!(res_a.is_some()); - assert!(res_b.is_some()); - }, - |res_a: Option>, - res_b: Option>, - res_c: Option>| { - assert!(res_a.is_some()); - assert!(res_b.is_some()); - assert!(res_c.is_some()); - }, - ) - .chain(), - ) - .chain(), - ); - }); - - run_schedule(6, |schedule: &mut Schedule| { - schedule.add_systems( - ( - ( - |mut commands: Commands| commands.insert_resource(Ra), - |mut commands: Commands, res_a: Option>| { - commands.insert_resource(Rb); - assert!(res_a.is_some()); - }, - ) - .chain(), - ( - |mut commands: Commands, - res_a: Option>, - res_b: Option>| { - commands.insert_resource(Rc); - assert!(res_a.is_some()); - assert!(res_b.is_none()); - }, - |res_a: Option>, - res_b: Option>, - res_c: Option>| { - assert!(res_a.is_some()); - assert!(res_b.is_some()); - assert!(res_c.is_some()); - }, - ) - .chain(), - ) - .chain_ignore_deferred(), - ); - }); - } - } - - #[derive(ScheduleLabel, Hash, Debug, Clone, PartialEq, Eq)] - struct TestSchedule; - - #[derive(Resource)] - struct CheckSystemRan(usize); - - #[test] - fn add_systems_to_existing_schedule() { - let mut schedules = Schedules::default(); - let schedule = Schedule::new(TestSchedule); - - schedules.insert(schedule); - schedules.add_systems(TestSchedule, |mut ran: ResMut| ran.0 += 1); - - let mut world = World::new(); - - world.insert_resource(CheckSystemRan(0)); - world.insert_resource(schedules); - world.run_schedule(TestSchedule); - - let value = world - .get_resource::() - .expect("CheckSystemRan Resource Should Exist"); - assert_eq!(value.0, 1); - } - - #[test] - fn add_systems_to_non_existing_schedule() { - let mut schedules = Schedules::default(); - - schedules.add_systems(TestSchedule, |mut ran: ResMut| ran.0 += 1); - - let mut world = World::new(); - - world.insert_resource(CheckSystemRan(0)); - world.insert_resource(schedules); - world.run_schedule(TestSchedule); - - let value = world - .get_resource::() - .expect("CheckSystemRan Resource Should Exist"); - assert_eq!(value.0, 1); - } - - #[derive(SystemSet, Debug, Hash, Clone, PartialEq, Eq)] - enum TestSet { - First, - Second, - } - - #[test] - fn configure_set_on_existing_schedule() { - let mut schedules = Schedules::default(); - let schedule = Schedule::new(TestSchedule); - - schedules.insert(schedule); - - schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain()); - schedules.add_systems( - TestSchedule, - (|mut ran: ResMut| { - assert_eq!(ran.0, 0); - ran.0 += 1; - }) - .in_set(TestSet::First), - ); - - schedules.add_systems( - TestSchedule, - (|mut ran: ResMut| { - assert_eq!(ran.0, 1); - ran.0 += 1; - }) - .in_set(TestSet::Second), - ); - - let mut world = World::new(); - - world.insert_resource(CheckSystemRan(0)); - world.insert_resource(schedules); - world.run_schedule(TestSchedule); - - let value = world - .get_resource::() - .expect("CheckSystemRan Resource Should Exist"); - assert_eq!(value.0, 2); - } - - #[test] - fn configure_set_on_new_schedule() { - let mut schedules = Schedules::default(); - - schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain()); - schedules.add_systems( - TestSchedule, - (|mut ran: ResMut| { - assert_eq!(ran.0, 0); - ran.0 += 1; - }) - .in_set(TestSet::First), - ); - - schedules.add_systems( - TestSchedule, - (|mut ran: ResMut| { - assert_eq!(ran.0, 1); - ran.0 += 1; - }) - .in_set(TestSet::Second), - ); - - let mut world = World::new(); - - world.insert_resource(CheckSystemRan(0)); - world.insert_resource(schedules); - world.run_schedule(TestSchedule); - - let value = world - .get_resource::() - .expect("CheckSystemRan Resource Should Exist"); - assert_eq!(value.0, 2); - } - - #[test] - fn test_default_error_handler() { - #[derive(Resource, Default)] - struct Ran(bool); - - fn system(mut ran: ResMut) -> Result { - ran.0 = true; - Err("I failed!".into()) - } - - // Test that the default error handler is used - let mut world = World::default(); - world.init_resource::(); - world.insert_resource(DefaultErrorHandler(ignore)); - let mut schedule = Schedule::default(); - schedule.add_systems(system).run(&mut world); - assert!(world.resource::().0); - - // Test that the handler doesn't change within the schedule - schedule.add_systems( - (|world: &mut World| { - world.insert_resource(DefaultErrorHandler(panic)); - }) - .before(system), - ); - schedule.run(&mut world); - } -} diff --git a/src/schedule/set.rs b/src/schedule/set.rs deleted file mode 100644 index b0a3e95..0000000 --- a/src/schedule/set.rs +++ /dev/null @@ -1,570 +0,0 @@ -use alloc::boxed::Box; -use bevy_utils::prelude::DebugName; -use core::{ - any::TypeId, - fmt::Debug, - hash::{Hash, Hasher}, - marker::PhantomData, -}; - -pub use crate::label::DynEq; -pub use bevy_ecs_macros::{ScheduleLabel, SystemSet}; - -use crate::{ - define_label, - intern::Interned, - system::{ - ExclusiveFunctionSystem, ExclusiveSystemParamFunction, FunctionSystem, IntoResult, - IsExclusiveFunctionSystem, IsFunctionSystem, SystemParamFunction, - }, -}; - -define_label!( - /// A strongly-typed class of labels used to identify a [`Schedule`]. - /// - /// Each schedule in a [`World`] has a unique schedule label value, and - /// schedules can be automatically created from labels via [`Schedules::add_systems()`]. - /// - /// # Defining new schedule labels - /// - /// By default, you should use Bevy's premade schedule labels which implement this trait. - /// If you are using [`bevy_ecs`] directly or if you need to run a group of systems outside - /// the existing schedules, you may define your own schedule labels by using - /// `#[derive(ScheduleLabel)]`. - /// - /// ``` - /// use bevy_ecs::prelude::*; - /// use bevy_ecs::schedule::ScheduleLabel; - /// - /// // Declare a new schedule label. - /// #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] - /// struct Update; - /// - /// let mut world = World::new(); - /// - /// // Add a system to the schedule with that label (creating it automatically). - /// fn a_system_function() {} - /// world.get_resource_or_init::().add_systems(Update, a_system_function); - /// - /// // Run the schedule, and therefore run the system. - /// world.run_schedule(Update); - /// ``` - /// - /// [`Schedule`]: crate::schedule::Schedule - /// [`Schedules::add_systems()`]: crate::schedule::Schedules::add_systems - /// [`World`]: crate::world::World - #[diagnostic::on_unimplemented( - note = "consider annotating `{Self}` with `#[derive(ScheduleLabel)]`" - )] - ScheduleLabel, - SCHEDULE_LABEL_INTERNER -); - -define_label!( - /// System sets are tag-like labels that can be used to group systems together. - /// - /// This allows you to share configuration (like run conditions) across multiple systems, - /// and order systems or system sets relative to conceptual groups of systems. - /// To control the behavior of a system set as a whole, use [`Schedule::configure_sets`](crate::prelude::Schedule::configure_sets), - /// or the method of the same name on `App`. - /// - /// Systems can belong to any number of system sets, reflecting multiple roles or facets that they might have. - /// For example, you may want to annotate a system as "consumes input" and "applies forces", - /// and ensure that your systems are ordered correctly for both of those sets. - /// - /// System sets can belong to any number of other system sets, - /// allowing you to create nested hierarchies of system sets to group systems together. - /// Configuration applied to system sets will flow down to their members (including other system sets), - /// allowing you to set and modify the configuration in a single place. - /// - /// Systems sets are also useful for exposing a consistent public API for dependencies - /// to hook into across versions of your crate, - /// allowing them to add systems to a specific set, or order relative to that set, - /// without leaking implementation details of the exact systems involved. - /// - /// ## Defining new system sets - /// - /// To create a new system set, use the `#[derive(SystemSet)]` macro. - /// Unit structs are a good choice for one-off sets. - /// - /// ```rust - /// # use bevy_ecs::prelude::*; - /// - /// #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] - /// struct PhysicsSystems; - /// ``` - /// - /// When you want to define several related system sets, - /// consider creating an enum system set. - /// Each variant will be treated as a separate system set. - /// - /// ```rust - /// # use bevy_ecs::prelude::*; - /// - /// #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] - /// enum CombatSystems { - /// TargetSelection, - /// DamageCalculation, - /// Cleanup, - /// } - /// ``` - /// - /// By convention, the listed order of the system set in the enum - /// corresponds to the order in which the systems are run. - /// Ordering must be explicitly added to ensure that this is the case, - /// but following this convention will help avoid confusion. - /// - /// ### Adding systems to system sets - /// - /// To add systems to a system set, call [`in_set`](crate::prelude::IntoScheduleConfigs::in_set) on the system function - /// while adding it to your app or schedule. - /// - /// Like usual, these methods can be chained with other configuration methods like [`before`](crate::prelude::IntoScheduleConfigs::before), - /// or repeated to add systems to multiple sets. - /// - /// ```rust - /// use bevy_ecs::prelude::*; - /// - /// #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] - /// enum CombatSystems { - /// TargetSelection, - /// DamageCalculation, - /// Cleanup, - /// } - /// - /// fn target_selection() {} - /// - /// fn enemy_damage_calculation() {} - /// - /// fn player_damage_calculation() {} - /// - /// let mut schedule = Schedule::default(); - /// // Configuring the sets to run in order. - /// schedule.configure_sets((CombatSystems::TargetSelection, CombatSystems::DamageCalculation, CombatSystems::Cleanup).chain()); - /// - /// // Adding a single system to a set. - /// schedule.add_systems(target_selection.in_set(CombatSystems::TargetSelection)); - /// - /// // Adding multiple systems to a set. - /// schedule.add_systems((player_damage_calculation, enemy_damage_calculation).in_set(CombatSystems::DamageCalculation)); - /// ``` - #[diagnostic::on_unimplemented( - note = "consider annotating `{Self}` with `#[derive(SystemSet)]`" - )] - SystemSet, - SYSTEM_SET_INTERNER, - extra_methods: { - /// Returns `Some` if this system set is a [`SystemTypeSet`]. - fn system_type(&self) -> Option { - None - } - - /// Returns `true` if this system set is an [`AnonymousSet`]. - fn is_anonymous(&self) -> bool { - false - } - }, - extra_methods_impl: { - fn system_type(&self) -> Option { - (**self).system_type() - } - - fn is_anonymous(&self) -> bool { - (**self).is_anonymous() - } - } -); - -/// A shorthand for `Interned`. -pub type InternedSystemSet = Interned; -/// A shorthand for `Interned`. -pub type InternedScheduleLabel = Interned; - -/// A [`SystemSet`] grouping instances of the same function. -/// -/// This kind of set is automatically populated and thus has some special rules: -/// - You cannot manually add members. -/// - You cannot configure them. -/// - You cannot order something relative to one if it has more than one member. -pub struct SystemTypeSet(PhantomData T>); - -impl SystemTypeSet { - pub(crate) fn new() -> Self { - Self(PhantomData) - } -} - -impl Debug for SystemTypeSet { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("SystemTypeSet") - .field(&format_args!("fn {}()", DebugName::type_name::())) - .finish() - } -} - -impl Hash for SystemTypeSet { - fn hash(&self, _state: &mut H) { - // all systems of a given type are the same - } -} - -impl Clone for SystemTypeSet { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for SystemTypeSet {} - -impl PartialEq for SystemTypeSet { - #[inline] - fn eq(&self, _other: &Self) -> bool { - // all systems of a given type are the same - true - } -} - -impl Eq for SystemTypeSet {} - -impl SystemSet for SystemTypeSet { - fn system_type(&self) -> Option { - Some(TypeId::of::()) - } - - fn dyn_clone(&self) -> Box { - Box::new(*self) - } -} - -/// A [`SystemSet`] implicitly created when using -/// [`Schedule::add_systems`](super::Schedule::add_systems) or -/// [`Schedule::configure_sets`](super::Schedule::configure_sets). -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub struct AnonymousSet(usize); - -impl AnonymousSet { - pub(crate) fn new(id: usize) -> Self { - Self(id) - } -} - -impl SystemSet for AnonymousSet { - fn is_anonymous(&self) -> bool { - true - } - - fn dyn_clone(&self) -> Box { - Box::new(*self) - } -} - -/// Types that can be converted into a [`SystemSet`]. -/// -/// # Usage notes -/// -/// This trait should only be used as a bound for trait implementations or as an -/// argument to a function. If a system set needs to be returned from a function -/// or stored somewhere, use [`SystemSet`] instead of this trait. -#[diagnostic::on_unimplemented( - message = "`{Self}` is not a system set", - label = "invalid system set" -)] -pub trait IntoSystemSet: Sized { - /// The type of [`SystemSet`] this instance converts into. - type Set: SystemSet; - - /// Converts this instance to its associated [`SystemSet`] type. - fn into_system_set(self) -> Self::Set; -} - -// systems sets -impl IntoSystemSet<()> for S { - type Set = Self; - - #[inline] - fn into_system_set(self) -> Self::Set { - self - } -} - -// systems -impl IntoSystemSet<(IsFunctionSystem, Marker)> for F -where - Marker: 'static, - F::Out: IntoResult<()>, - F: SystemParamFunction, -{ - type Set = SystemTypeSet>; - - #[inline] - fn into_system_set(self) -> Self::Set { - SystemTypeSet::>::new() - } -} - -// exclusive systems -impl IntoSystemSet<(IsExclusiveFunctionSystem, Marker)> for F -where - Marker: 'static, - F::Out: IntoResult<()>, - F: ExclusiveSystemParamFunction, -{ - type Set = SystemTypeSet>; - - #[inline] - fn into_system_set(self) -> Self::Set { - SystemTypeSet::>::new() - } -} - -#[cfg(test)] -mod tests { - use crate::{ - resource::Resource, - schedule::{tests::ResMut, Schedule}, - system::{IntoSystem, System}, - }; - - use super::*; - - #[test] - fn test_schedule_label() { - use crate::world::World; - - #[derive(Resource)] - struct Flag(bool); - - #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct A; - - #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct B; - - let mut world = World::new(); - - let mut schedule = Schedule::new(A); - schedule.add_systems(|mut flag: ResMut| flag.0 = true); - world.add_schedule(schedule); - - let interned = A.intern(); - - world.insert_resource(Flag(false)); - world.run_schedule(interned); - assert!(world.resource::().0); - - world.insert_resource(Flag(false)); - world.run_schedule(interned); - assert!(world.resource::().0); - - assert_ne!(A.intern(), B.intern()); - } - - #[test] - fn test_derive_schedule_label() { - #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct UnitLabel; - - #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct TupleLabel(u32, u32); - - #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct StructLabel { - a: u32, - b: u32, - } - - #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct EmptyTupleLabel(); - - #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct EmptyStructLabel {} - - #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - enum EnumLabel { - #[default] - Unit, - Tuple(u32, u32), - Struct { - a: u32, - b: u32, - }, - } - - #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct GenericLabel(PhantomData); - - assert_eq!(UnitLabel.intern(), UnitLabel.intern()); - assert_eq!(EnumLabel::Unit.intern(), EnumLabel::Unit.intern()); - assert_ne!(UnitLabel.intern(), EnumLabel::Unit.intern()); - assert_ne!(UnitLabel.intern(), TupleLabel(0, 0).intern()); - assert_ne!(EnumLabel::Unit.intern(), EnumLabel::Tuple(0, 0).intern()); - - assert_eq!(TupleLabel(0, 0).intern(), TupleLabel(0, 0).intern()); - assert_eq!( - EnumLabel::Tuple(0, 0).intern(), - EnumLabel::Tuple(0, 0).intern() - ); - assert_ne!(TupleLabel(0, 0).intern(), TupleLabel(0, 1).intern()); - assert_ne!( - EnumLabel::Tuple(0, 0).intern(), - EnumLabel::Tuple(0, 1).intern() - ); - assert_ne!(TupleLabel(0, 0).intern(), EnumLabel::Tuple(0, 0).intern()); - assert_ne!( - TupleLabel(0, 0).intern(), - StructLabel { a: 0, b: 0 }.intern() - ); - assert_ne!( - EnumLabel::Tuple(0, 0).intern(), - EnumLabel::Struct { a: 0, b: 0 }.intern() - ); - - assert_eq!( - StructLabel { a: 0, b: 0 }.intern(), - StructLabel { a: 0, b: 0 }.intern() - ); - assert_eq!( - EnumLabel::Struct { a: 0, b: 0 }.intern(), - EnumLabel::Struct { a: 0, b: 0 }.intern() - ); - assert_ne!( - StructLabel { a: 0, b: 0 }.intern(), - StructLabel { a: 0, b: 1 }.intern() - ); - assert_ne!( - EnumLabel::Struct { a: 0, b: 0 }.intern(), - EnumLabel::Struct { a: 0, b: 1 }.intern() - ); - assert_ne!( - StructLabel { a: 0, b: 0 }.intern(), - EnumLabel::Struct { a: 0, b: 0 }.intern() - ); - assert_ne!( - StructLabel { a: 0, b: 0 }.intern(), - EnumLabel::Struct { a: 0, b: 0 }.intern() - ); - assert_ne!(StructLabel { a: 0, b: 0 }.intern(), UnitLabel.intern(),); - assert_ne!( - EnumLabel::Struct { a: 0, b: 0 }.intern(), - EnumLabel::Unit.intern() - ); - - assert_eq!( - GenericLabel::(PhantomData).intern(), - GenericLabel::(PhantomData).intern() - ); - assert_ne!( - GenericLabel::(PhantomData).intern(), - GenericLabel::(PhantomData).intern() - ); - } - - #[test] - fn test_derive_system_set() { - #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct UnitSet; - - #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct TupleSet(u32, u32); - - #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct StructSet { - a: u32, - b: u32, - } - - #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct EmptyTupleSet(); - - #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct EmptyStructSet {} - - #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - enum EnumSet { - #[default] - Unit, - Tuple(u32, u32), - Struct { - a: u32, - b: u32, - }, - } - - #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] - struct GenericSet(PhantomData); - - assert_eq!(UnitSet.intern(), UnitSet.intern()); - assert_eq!(EnumSet::Unit.intern(), EnumSet::Unit.intern()); - assert_ne!(UnitSet.intern(), EnumSet::Unit.intern()); - assert_ne!(UnitSet.intern(), TupleSet(0, 0).intern()); - assert_ne!(EnumSet::Unit.intern(), EnumSet::Tuple(0, 0).intern()); - - assert_eq!(TupleSet(0, 0).intern(), TupleSet(0, 0).intern()); - assert_eq!(EnumSet::Tuple(0, 0).intern(), EnumSet::Tuple(0, 0).intern()); - assert_ne!(TupleSet(0, 0).intern(), TupleSet(0, 1).intern()); - assert_ne!(EnumSet::Tuple(0, 0).intern(), EnumSet::Tuple(0, 1).intern()); - assert_ne!(TupleSet(0, 0).intern(), EnumSet::Tuple(0, 0).intern()); - assert_ne!(TupleSet(0, 0).intern(), StructSet { a: 0, b: 0 }.intern()); - assert_ne!( - EnumSet::Tuple(0, 0).intern(), - EnumSet::Struct { a: 0, b: 0 }.intern() - ); - - assert_eq!( - StructSet { a: 0, b: 0 }.intern(), - StructSet { a: 0, b: 0 }.intern() - ); - assert_eq!( - EnumSet::Struct { a: 0, b: 0 }.intern(), - EnumSet::Struct { a: 0, b: 0 }.intern() - ); - assert_ne!( - StructSet { a: 0, b: 0 }.intern(), - StructSet { a: 0, b: 1 }.intern() - ); - assert_ne!( - EnumSet::Struct { a: 0, b: 0 }.intern(), - EnumSet::Struct { a: 0, b: 1 }.intern() - ); - assert_ne!( - StructSet { a: 0, b: 0 }.intern(), - EnumSet::Struct { a: 0, b: 0 }.intern() - ); - assert_ne!( - StructSet { a: 0, b: 0 }.intern(), - EnumSet::Struct { a: 0, b: 0 }.intern() - ); - assert_ne!(StructSet { a: 0, b: 0 }.intern(), UnitSet.intern(),); - assert_ne!( - EnumSet::Struct { a: 0, b: 0 }.intern(), - EnumSet::Unit.intern() - ); - - assert_eq!( - GenericSet::(PhantomData).intern(), - GenericSet::(PhantomData).intern() - ); - assert_ne!( - GenericSet::(PhantomData).intern(), - GenericSet::(PhantomData).intern() - ); - } - - #[test] - fn system_set_matches_default_system_set() { - fn system() {} - let set_from_into_system_set = IntoSystemSet::into_system_set(system).intern(); - let system = IntoSystem::into_system(system); - let set_from_system = system.default_system_sets()[0]; - assert_eq!(set_from_into_system_set, set_from_system); - } - - #[test] - fn system_set_matches_default_system_set_exclusive() { - fn system(_: &mut crate::world::World) {} - let set_from_into_system_set = IntoSystemSet::into_system_set(system).intern(); - let system = IntoSystem::into_system(system); - let set_from_system = system.default_system_sets()[0]; - assert_eq!(set_from_into_system_set, set_from_system); - } -} diff --git a/src/schedule/stepping.rs b/src/schedule/stepping.rs deleted file mode 100644 index d765ebe..0000000 --- a/src/schedule/stepping.rs +++ /dev/null @@ -1,1564 +0,0 @@ -use crate::{ - resource::Resource, - schedule::{InternedScheduleLabel, NodeId, Schedule, ScheduleLabel, SystemKey}, - system::{IntoSystem, ResMut}, -}; -use alloc::vec::Vec; -use bevy_platform::collections::HashMap; -use bevy_utils::TypeIdMap; -use core::any::TypeId; -use fixedbitset::FixedBitSet; -use log::{info, warn}; -use thiserror::Error; - -#[cfg(not(feature = "bevy_debug_stepping"))] -use log::error; - -#[cfg(test)] -use log::debug; - -#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] -enum Action { - /// Stepping is disabled; run all systems - #[default] - RunAll, - - /// Stepping is enabled, but we're only running required systems this frame - Waiting, - - /// Stepping is enabled; run all systems until the end of the frame, or - /// until we encounter a system marked with [`SystemBehavior::Break`] or all - /// systems in the frame have run. - Continue, - - /// stepping is enabled; only run the next system in our step list - Step, -} - -#[derive(Debug, Copy, Clone)] -enum SystemBehavior { - /// System will always run regardless of stepping action - AlwaysRun, - - /// System will never run while stepping is enabled - NeverRun, - - /// When [`Action::Waiting`] this system will not be run - /// When [`Action::Step`] this system will be stepped - /// When [`Action::Continue`] system execution will stop before executing - /// this system unless its the first system run when continuing - Break, - - /// When [`Action::Waiting`] this system will not be run - /// When [`Action::Step`] this system will be stepped - /// When [`Action::Continue`] this system will be run - Continue, -} - -// schedule_order index, and schedule start point -#[derive(Debug, Default, Clone, Copy)] -struct Cursor { - /// index within `Stepping::schedule_order` - pub schedule: usize, - /// index within the schedule's system list - pub system: usize, -} - -// Two methods of referring to Systems, via TypeId, or per-Schedule NodeId -enum SystemIdentifier { - Type(TypeId), - Node(NodeId), -} - -/// Updates to [`Stepping.schedule_states`] that will be applied at the start -/// of the next render frame -enum Update { - /// Set the action stepping will perform for this render frame - SetAction(Action), - /// Enable stepping for this schedule - AddSchedule(InternedScheduleLabel), - /// Disable stepping for this schedule - RemoveSchedule(InternedScheduleLabel), - /// Clear any system-specific behaviors for this schedule - ClearSchedule(InternedScheduleLabel), - /// Set a system-specific behavior for this schedule & system - SetBehavior(InternedScheduleLabel, SystemIdentifier, SystemBehavior), - /// Clear any system-specific behavior for this schedule & system - ClearBehavior(InternedScheduleLabel, SystemIdentifier), -} - -#[derive(Error, Debug)] -#[error("not available until all configured schedules have been run; try again next frame")] -pub struct NotReady; - -#[derive(Resource, Default)] -/// Resource for controlling system stepping behavior -pub struct Stepping { - // [`ScheduleState`] for each [`Schedule`] with stepping enabled - schedule_states: HashMap, - - // dynamically generated [`Schedule`] order - schedule_order: Vec, - - // current position in the stepping frame - cursor: Cursor, - - // index in [`schedule_order`] of the last schedule to call `skipped_systems()` - previous_schedule: Option, - - // Action to perform during this render frame - action: Action, - - // Updates apply at the start of the next render frame - updates: Vec, -} - -impl core::fmt::Debug for Stepping { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "Stepping {{ action: {:?}, schedules: {:?}, order: {:?}", - self.action, - self.schedule_states.keys(), - self.schedule_order - )?; - if self.action != Action::RunAll { - let Cursor { schedule, system } = self.cursor; - match self.schedule_order.get(schedule) { - Some(label) => write!(f, "cursor: {label:?}[{system}], ")?, - None => write!(f, "cursor: None, ")?, - }; - } - write!(f, "}}") - } -} - -impl Stepping { - /// Create a new instance of the `Stepping` resource. - pub fn new() -> Self { - Stepping::default() - } - - /// System to call denoting that a new render frame has begun - /// - /// Note: This system is automatically added to the default `MainSchedule`. - pub fn begin_frame(stepping: Option>) { - if let Some(mut stepping) = stepping { - stepping.next_frame(); - } - } - - /// Return the list of schedules with stepping enabled in the order - /// they are executed in. - pub fn schedules(&self) -> Result<&Vec, NotReady> { - if self.schedule_order.len() == self.schedule_states.len() { - Ok(&self.schedule_order) - } else { - Err(NotReady) - } - } - - /// Return our current position within the stepping frame - /// - /// NOTE: This function **will** return `None` during normal execution with - /// stepping enabled. This can happen at the end of the stepping frame - /// after the last system has been run, but before the start of the next - /// render frame. - pub fn cursor(&self) -> Option<(InternedScheduleLabel, NodeId)> { - if self.action == Action::RunAll { - return None; - } - let label = self.schedule_order.get(self.cursor.schedule)?; - let state = self.schedule_states.get(label)?; - state - .node_ids - .get(self.cursor.system) - .map(|node_id| (*label, NodeId::System(*node_id))) - } - - /// Enable stepping for the provided schedule - pub fn add_schedule(&mut self, schedule: impl ScheduleLabel) -> &mut Self { - self.updates.push(Update::AddSchedule(schedule.intern())); - self - } - - /// Disable stepping for the provided schedule - /// - /// NOTE: This function will also clear any system-specific behaviors that - /// may have been configured. - pub fn remove_schedule(&mut self, schedule: impl ScheduleLabel) -> &mut Self { - self.updates.push(Update::RemoveSchedule(schedule.intern())); - self - } - - /// Clear behavior set for all systems in the provided [`Schedule`] - pub fn clear_schedule(&mut self, schedule: impl ScheduleLabel) -> &mut Self { - self.updates.push(Update::ClearSchedule(schedule.intern())); - self - } - - /// Begin stepping at the start of the next frame - pub fn enable(&mut self) -> &mut Self { - #[cfg(feature = "bevy_debug_stepping")] - self.updates.push(Update::SetAction(Action::Waiting)); - #[cfg(not(feature = "bevy_debug_stepping"))] - error!( - "Stepping cannot be enabled; \ - bevy was compiled without the bevy_debug_stepping feature" - ); - self - } - - /// Disable stepping, resume normal systems execution - pub fn disable(&mut self) -> &mut Self { - self.updates.push(Update::SetAction(Action::RunAll)); - self - } - - /// Check if stepping is enabled - pub fn is_enabled(&self) -> bool { - self.action != Action::RunAll - } - - /// Run the next system during the next render frame - /// - /// NOTE: This will have no impact unless stepping has been enabled - pub fn step_frame(&mut self) -> &mut Self { - self.updates.push(Update::SetAction(Action::Step)); - self - } - - /// Run all remaining systems in the stepping frame during the next render - /// frame - /// - /// NOTE: This will have no impact unless stepping has been enabled - pub fn continue_frame(&mut self) -> &mut Self { - self.updates.push(Update::SetAction(Action::Continue)); - self - } - - /// Ensure this system always runs when stepping is enabled - /// - /// Note: if the system is run multiple times in the [`Schedule`], this - /// will apply for all instances of the system. - pub fn always_run( - &mut self, - schedule: impl ScheduleLabel, - system: impl IntoSystem<(), (), Marker>, - ) -> &mut Self { - let type_id = system.system_type_id(); - self.updates.push(Update::SetBehavior( - schedule.intern(), - SystemIdentifier::Type(type_id), - SystemBehavior::AlwaysRun, - )); - - self - } - - /// Ensure this system instance always runs when stepping is enabled - pub fn always_run_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self { - self.updates.push(Update::SetBehavior( - schedule.intern(), - SystemIdentifier::Node(node), - SystemBehavior::AlwaysRun, - )); - self - } - - /// Ensure this system never runs when stepping is enabled - pub fn never_run( - &mut self, - schedule: impl ScheduleLabel, - system: impl IntoSystem<(), (), Marker>, - ) -> &mut Self { - let type_id = system.system_type_id(); - self.updates.push(Update::SetBehavior( - schedule.intern(), - SystemIdentifier::Type(type_id), - SystemBehavior::NeverRun, - )); - - self - } - - /// Ensure this system instance never runs when stepping is enabled - pub fn never_run_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self { - self.updates.push(Update::SetBehavior( - schedule.intern(), - SystemIdentifier::Node(node), - SystemBehavior::NeverRun, - )); - self - } - - /// Add a breakpoint for system - pub fn set_breakpoint( - &mut self, - schedule: impl ScheduleLabel, - system: impl IntoSystem<(), (), Marker>, - ) -> &mut Self { - let type_id = system.system_type_id(); - self.updates.push(Update::SetBehavior( - schedule.intern(), - SystemIdentifier::Type(type_id), - SystemBehavior::Break, - )); - - self - } - - /// Add a breakpoint for system instance - pub fn set_breakpoint_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self { - self.updates.push(Update::SetBehavior( - schedule.intern(), - SystemIdentifier::Node(node), - SystemBehavior::Break, - )); - self - } - - /// Clear a breakpoint for the system - pub fn clear_breakpoint( - &mut self, - schedule: impl ScheduleLabel, - system: impl IntoSystem<(), (), Marker>, - ) -> &mut Self { - self.clear_system(schedule, system); - - self - } - - /// clear a breakpoint for system instance - pub fn clear_breakpoint_node( - &mut self, - schedule: impl ScheduleLabel, - node: NodeId, - ) -> &mut Self { - self.clear_node(schedule, node); - self - } - - /// Clear any behavior set for the system - pub fn clear_system( - &mut self, - schedule: impl ScheduleLabel, - system: impl IntoSystem<(), (), Marker>, - ) -> &mut Self { - let type_id = system.system_type_id(); - self.updates.push(Update::ClearBehavior( - schedule.intern(), - SystemIdentifier::Type(type_id), - )); - - self - } - - /// clear a breakpoint for system instance - pub fn clear_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self { - self.updates.push(Update::ClearBehavior( - schedule.intern(), - SystemIdentifier::Node(node), - )); - self - } - - /// lookup the first system for the supplied schedule index - fn first_system_index_for_schedule(&self, index: usize) -> usize { - let label = match self.schedule_order.get(index) { - None => return 0, - Some(label) => label, - }; - let state = match self.schedule_states.get(label) { - None => return 0, - Some(state) => state, - }; - state.first.unwrap_or(0) - } - - /// Move the cursor to the start of the first schedule - fn reset_cursor(&mut self) { - self.cursor = Cursor { - schedule: 0, - system: self.first_system_index_for_schedule(0), - }; - } - - /// Advance schedule states for the next render frame - fn next_frame(&mut self) { - // if stepping is enabled; reset our internal state for the start of - // the next frame - if self.action != Action::RunAll { - self.action = Action::Waiting; - self.previous_schedule = None; - - // if the cursor passed the last schedule, reset it - if self.cursor.schedule >= self.schedule_order.len() { - self.reset_cursor(); - } - } - - if self.updates.is_empty() { - return; - } - - let mut reset_cursor = false; - for update in self.updates.drain(..) { - match update { - Update::SetAction(Action::RunAll) => { - self.action = Action::RunAll; - reset_cursor = true; - } - Update::SetAction(action) => { - // This match block is really just to filter out invalid - // transitions, and add debugging messages for permitted - // transitions. Any action transition that falls through - // this match block will be performed. - #[expect( - clippy::match_same_arms, - reason = "Readability would be negatively impacted by combining the `(Waiting, RunAll)` and `(Continue, RunAll)` match arms." - )] - match (self.action, action) { - // ignore non-transition updates, and prevent a call to - // enable() from overwriting a step or continue call - (Action::RunAll, Action::RunAll) - | (Action::Waiting, Action::Waiting) - | (Action::Continue, Action::Continue) - | (Action::Step, Action::Step) - | (Action::Continue, Action::Waiting) - | (Action::Step, Action::Waiting) => continue, - - // when stepping is disabled - (Action::RunAll, Action::Waiting) => info!("enabled stepping"), - (Action::RunAll, _) => { - warn!( - "stepping not enabled; call Stepping::enable() \ - before step_frame() or continue_frame()" - ); - continue; - } - - // stepping enabled; waiting - (Action::Waiting, Action::RunAll) => info!("disabled stepping"), - (Action::Waiting, Action::Continue) => info!("continue frame"), - (Action::Waiting, Action::Step) => info!("step frame"), - - // stepping enabled; continue frame - (Action::Continue, Action::RunAll) => info!("disabled stepping"), - (Action::Continue, Action::Step) => { - warn!("ignoring step_frame(); already continuing next frame"); - continue; - } - - // stepping enabled; step frame - (Action::Step, Action::RunAll) => info!("disabled stepping"), - (Action::Step, Action::Continue) => { - warn!("ignoring continue_frame(); already stepping next frame"); - continue; - } - } - - // permitted action transition; make the change - self.action = action; - } - Update::AddSchedule(l) => { - self.schedule_states.insert(l, ScheduleState::default()); - } - Update::RemoveSchedule(label) => { - self.schedule_states.remove(&label); - if let Some(index) = self.schedule_order.iter().position(|l| l == &label) { - self.schedule_order.remove(index); - } - reset_cursor = true; - } - Update::ClearSchedule(label) => match self.schedule_states.get_mut(&label) { - Some(state) => state.clear_behaviors(), - None => { - warn!( - "stepping is not enabled for schedule {label:?}; \ - use `.add_stepping({label:?})` to enable stepping" - ); - } - }, - Update::SetBehavior(label, system, behavior) => { - match self.schedule_states.get_mut(&label) { - Some(state) => state.set_behavior(system, behavior), - None => { - warn!( - "stepping is not enabled for schedule {label:?}; \ - use `.add_stepping({label:?})` to enable stepping" - ); - } - } - } - Update::ClearBehavior(label, system) => { - match self.schedule_states.get_mut(&label) { - Some(state) => state.clear_behavior(system), - None => { - warn!( - "stepping is not enabled for schedule {label:?}; \ - use `.add_stepping({label:?})` to enable stepping" - ); - } - } - } - } - } - - if reset_cursor { - self.reset_cursor(); - } - } - - /// get the list of systems this schedule should skip for this render - /// frame - pub fn skipped_systems(&mut self, schedule: &Schedule) -> Option { - if self.action == Action::RunAll { - return None; - } - - // grab the label and state for this schedule - let label = schedule.label(); - let state = self.schedule_states.get_mut(&label)?; - - // Stepping is enabled, and this schedule is supposed to be stepped. - // - // We need to maintain a list of schedules in the order that they call - // this function. We'll check the ordered list now to see if this - // schedule is present. If not, we'll add it after the last schedule - // that called this function. Finally we want to save off the index of - // this schedule in the ordered schedule list. This is used to - // determine if this is the schedule the cursor is pointed at. - let index = self.schedule_order.iter().position(|l| *l == label); - let index = match (index, self.previous_schedule) { - (Some(index), _) => index, - (None, None) => { - self.schedule_order.insert(0, label); - 0 - } - (None, Some(last)) => { - self.schedule_order.insert(last + 1, label); - last + 1 - } - }; - // Update the index of the previous schedule to be the index of this - // schedule for the next call - self.previous_schedule = Some(index); - - #[cfg(test)] - debug!( - "cursor {:?}, index {}, label {:?}", - self.cursor, index, label - ); - - // if the stepping frame cursor is pointing at this schedule, we'll run - // the schedule with the current stepping action. If this is not the - // cursor schedule, we'll run the schedule with the waiting action. - let cursor = self.cursor; - let (skip_list, next_system) = if index == cursor.schedule { - let (skip_list, next_system) = - state.skipped_systems(schedule, cursor.system, self.action); - - // if we just stepped this schedule, then we'll switch the action - // to be waiting - if self.action == Action::Step { - self.action = Action::Waiting; - } - (skip_list, next_system) - } else { - // we're not supposed to run any systems in this schedule, so pull - // the skip list, but ignore any changes it makes to the cursor. - let (skip_list, _) = state.skipped_systems(schedule, 0, Action::Waiting); - (skip_list, Some(cursor.system)) - }; - - // update the stepping frame cursor based on if there are any systems - // remaining to be run in the schedule - // Note: Don't try to detect the end of the render frame here using the - // schedule index. We don't know all schedules have been added to the - // schedule_order, so only next_frame() knows its safe to reset the - // cursor. - match next_system { - Some(i) => self.cursor.system = i, - None => { - let index = cursor.schedule + 1; - self.cursor = Cursor { - schedule: index, - system: self.first_system_index_for_schedule(index), - }; - - #[cfg(test)] - debug!("advanced schedule index: {} -> {}", cursor.schedule, index); - } - } - - Some(skip_list) - } -} - -#[derive(Default)] -struct ScheduleState { - /// per-system [`SystemBehavior`] - behaviors: HashMap, - - /// order of [`NodeId`]s in the schedule - /// - /// This is a cached copy of `SystemExecutable::system_ids`. We need it - /// available here to be accessed by [`Stepping::cursor()`] so we can return - /// [`NodeId`]s to the caller. - node_ids: Vec, - - /// changes to system behavior that should be applied the next time - /// [`ScheduleState::skipped_systems()`] is called - behavior_updates: TypeIdMap>, - - /// This field contains the first steppable system in the schedule. - first: Option, -} - -impl ScheduleState { - // set the stepping behavior for a system in this schedule - fn set_behavior(&mut self, system: SystemIdentifier, behavior: SystemBehavior) { - self.first = None; - match system { - SystemIdentifier::Node(node_id) => { - self.behaviors.insert(node_id, behavior); - } - // Behaviors are indexed by NodeId, but we cannot map a system - // TypeId to a NodeId without the `Schedule`. So queue this update - // to be processed the next time `skipped_systems()` is called. - SystemIdentifier::Type(type_id) => { - self.behavior_updates.insert(type_id, Some(behavior)); - } - } - } - - // clear the stepping behavior for a system in this schedule - fn clear_behavior(&mut self, system: SystemIdentifier) { - self.first = None; - match system { - SystemIdentifier::Node(node_id) => { - self.behaviors.remove(&node_id); - } - // queue TypeId updates to be processed later when we have Schedule - SystemIdentifier::Type(type_id) => { - self.behavior_updates.insert(type_id, None); - } - } - } - - // clear all system behaviors - fn clear_behaviors(&mut self) { - self.behaviors.clear(); - self.behavior_updates.clear(); - self.first = None; - } - - // apply system behavior updates by looking up the node id of the system in - // the schedule, and updating `systems` - fn apply_behavior_updates(&mut self, schedule: &Schedule) { - // Systems may be present multiple times within a schedule, so we - // iterate through all systems in the schedule, and check our behavior - // updates for the system TypeId. - // PERF: If we add a way to efficiently query schedule systems by their TypeId, we could remove the full - // system scan here - for (key, system) in schedule.systems().unwrap() { - let behavior = self.behavior_updates.get(&system.type_id()); - match behavior { - None => continue, - Some(None) => { - self.behaviors.remove(&NodeId::System(key)); - } - Some(Some(behavior)) => { - self.behaviors.insert(NodeId::System(key), *behavior); - } - } - } - self.behavior_updates.clear(); - - #[cfg(test)] - debug!("apply_updates(): {:?}", self.behaviors); - } - - fn skipped_systems( - &mut self, - schedule: &Schedule, - start: usize, - mut action: Action, - ) -> (FixedBitSet, Option) { - use core::cmp::Ordering; - - // if our NodeId list hasn't been populated, copy it over from the - // schedule - if self.node_ids.len() != schedule.systems_len() { - self.node_ids.clone_from(&schedule.executable().system_ids); - } - - // Now that we have the schedule, apply any pending system behavior - // updates. The schedule is required to map from system `TypeId` to - // `NodeId`. - if !self.behavior_updates.is_empty() { - self.apply_behavior_updates(schedule); - } - - // if we don't have a first system set, set it now - if self.first.is_none() { - for (i, (key, _)) in schedule.systems().unwrap().enumerate() { - match self.behaviors.get(&NodeId::System(key)) { - Some(SystemBehavior::AlwaysRun | SystemBehavior::NeverRun) => continue, - Some(_) | None => { - self.first = Some(i); - break; - } - } - } - } - - let mut skip = FixedBitSet::with_capacity(schedule.systems_len()); - let mut pos = start; - - for (i, (key, _system)) in schedule.systems().unwrap().enumerate() { - let behavior = self - .behaviors - .get(&NodeId::System(key)) - .unwrap_or(&SystemBehavior::Continue); - - #[cfg(test)] - debug!( - "skipped_systems(): systems[{}], pos {}, Action::{:?}, Behavior::{:?}, {}", - i, - pos, - action, - behavior, - _system.name() - ); - - match (action, behavior) { - // regardless of which action we're performing, if the system - // is marked as NeverRun, add it to the skip list. - // Also, advance the cursor past this system if it is our - // current position - (_, SystemBehavior::NeverRun) => { - skip.insert(i); - if i == pos { - pos += 1; - } - } - // similarly, ignore any system marked as AlwaysRun; they should - // never be added to the skip list - // Also, advance the cursor past this system if it is our - // current position - (_, SystemBehavior::AlwaysRun) => { - if i == pos { - pos += 1; - } - } - // if we're waiting, no other systems besides AlwaysRun should - // be run, so add systems to the skip list - (Action::Waiting, _) => skip.insert(i), - - // If we're stepping, the remaining behaviors don't matter, - // we're only going to run the system at our cursor. Any system - // prior to the cursor is skipped. Once we encounter the system - // at the cursor, we'll advance the cursor, and set behavior to - // Waiting to skip remaining systems. - (Action::Step, _) => match i.cmp(&pos) { - Ordering::Less => skip.insert(i), - Ordering::Equal => { - pos += 1; - action = Action::Waiting; - } - Ordering::Greater => unreachable!(), - }, - // If we're continuing, and the step behavior is continue, we - // want to skip any systems prior to our start position. That's - // where the stepping frame left off last time we ran anything. - (Action::Continue, SystemBehavior::Continue) => { - if i < start { - skip.insert(i); - } - } - // If we're continuing, and we encounter a breakpoint we may - // want to stop before executing the system. To do this we - // skip this system and set the action to Waiting. - // - // Note: if the cursor is pointing at this system, we will run - // it anyway. This allows the user to continue, hit a - // breakpoint, then continue again to run the breakpoint system - // and any following systems. - (Action::Continue, SystemBehavior::Break) => { - if i != start { - skip.insert(i); - - // stop running systems if the breakpoint isn't the - // system under the cursor. - if i > start { - action = Action::Waiting; - } - } - } - // should have never gotten into this method if stepping is - // disabled - (Action::RunAll, _) => unreachable!(), - } - - // If we're at the cursor position, and not waiting, advance the - // cursor. - if i == pos && action != Action::Waiting { - pos += 1; - } - } - - // output is the skip list, and the index of the next system to run in - // this schedule. - if pos >= schedule.systems_len() { - (skip, None) - } else { - (skip, Some(pos)) - } - } -} - -#[cfg(all(test, feature = "bevy_debug_stepping"))] -#[expect(clippy::print_stdout, reason = "Allowed in tests.")] -mod tests { - use super::*; - use crate::{prelude::*, schedule::ScheduleLabel}; - use alloc::{format, vec}; - use slotmap::SlotMap; - use std::println; - - #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] - struct TestSchedule; - - #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] - struct TestScheduleA; - - #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] - struct TestScheduleB; - - #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] - struct TestScheduleC; - - #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] - struct TestScheduleD; - - fn first_system() {} - fn second_system() {} - fn third_system() {} - - fn setup() -> (Schedule, World) { - let mut world = World::new(); - let mut schedule = Schedule::new(TestSchedule); - schedule.add_systems((first_system, second_system).chain()); - schedule.initialize(&mut world).unwrap(); - (schedule, world) - } - - // Helper for verifying skip_lists are equal, and if not, printing a human - // readable message. - macro_rules! assert_skip_list_eq { - ($actual:expr, $expected:expr, $system_names:expr) => { - let actual = $actual; - let expected = $expected; - let systems: &Vec<&str> = $system_names; - - if (actual != expected) { - use core::fmt::Write as _; - - // mismatch, let's construct a human-readable message of what - // was returned - let mut msg = format!( - "Schedule:\n {:9} {:16}{:6} {:6} {:6}\n", - "index", "name", "expect", "actual", "result" - ); - for (i, name) in systems.iter().enumerate() { - let _ = write!(msg, " system[{:1}] {:16}", i, name); - match (expected.contains(i), actual.contains(i)) { - (true, true) => msg.push_str("skip skip pass\n"), - (true, false) => { - msg.push_str("skip run FAILED; system should not have run\n") - } - (false, true) => { - msg.push_str("run skip FAILED; system should have run\n") - } - (false, false) => msg.push_str("run run pass\n"), - } - } - assert_eq!(actual, expected, "{}", msg); - } - }; - } - - // Helper for verifying that a set of systems will be run for a given skip - // list - macro_rules! assert_systems_run { - ($schedule:expr, $skipped_systems:expr, $($system:expr),*) => { - // pull an ordered list of systems in the schedule, and save the - // system TypeId, and name. - let systems: Vec<(TypeId, alloc::string::String)> = $schedule.systems().unwrap() - .map(|(_, system)| { - (system.type_id(), system.name().as_string()) - }) - .collect(); - - // construct a list of systems that are expected to run - let mut expected = FixedBitSet::with_capacity(systems.len()); - $( - let sys = IntoSystem::into_system($system); - for (i, (type_id, _)) in systems.iter().enumerate() { - if sys.type_id() == *type_id { - expected.insert(i); - } - } - )* - - // flip the run list to get our skip list - expected.toggle_range(..); - - // grab the list of skipped systems - let actual = match $skipped_systems { - None => FixedBitSet::with_capacity(systems.len()), - Some(b) => b, - }; - let system_names: Vec<&str> = systems - .iter() - .map(|(_,n)| n.rsplit_once("::").unwrap().1) - .collect(); - - assert_skip_list_eq!(actual, expected, &system_names); - }; - } - - // Helper for verifying the expected systems will be run by the schedule - // - // This macro will construct an expected FixedBitSet for the systems that - // should be skipped, and compare it with the results from stepping the - // provided schedule. If they don't match, it generates a human-readable - // error message and asserts. - macro_rules! assert_schedule_runs { - ($schedule:expr, $stepping:expr, $($system:expr),*) => { - // advance stepping to the next frame, and build the skip list for - // this schedule - $stepping.next_frame(); - assert_systems_run!($schedule, $stepping.skipped_systems($schedule), $($system),*); - }; - } - - #[test] - fn stepping_disabled() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping.add_schedule(TestSchedule).disable().next_frame(); - - assert!(stepping.skipped_systems(&schedule).is_none()); - assert!(stepping.cursor().is_none()); - } - - #[test] - fn unknown_schedule() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping.enable().next_frame(); - - assert!(stepping.skipped_systems(&schedule).is_none()); - } - - #[test] - fn disabled_always_run() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .disable() - .always_run(TestSchedule, first_system); - - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - } - - #[test] - fn waiting_always_run() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .always_run(TestSchedule, first_system); - - assert_schedule_runs!(&schedule, &mut stepping, first_system); - } - - #[test] - fn step_always_run() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .always_run(TestSchedule, first_system) - .step_frame(); - - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - } - - #[test] - fn continue_always_run() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .always_run(TestSchedule, first_system) - .continue_frame(); - - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - } - - #[test] - fn disabled_never_run() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .never_run(TestSchedule, first_system) - .disable(); - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - } - - #[test] - fn waiting_never_run() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .never_run(TestSchedule, first_system); - - assert_schedule_runs!(&schedule, &mut stepping,); - } - - #[test] - fn step_never_run() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .never_run(TestSchedule, first_system) - .step_frame(); - - assert_schedule_runs!(&schedule, &mut stepping, second_system); - } - - #[test] - fn continue_never_run() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .never_run(TestSchedule, first_system) - .continue_frame(); - - assert_schedule_runs!(&schedule, &mut stepping, second_system); - } - - #[test] - fn disabled_breakpoint() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .disable() - .set_breakpoint(TestSchedule, second_system); - - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - } - - #[test] - fn waiting_breakpoint() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .set_breakpoint(TestSchedule, second_system); - - assert_schedule_runs!(&schedule, &mut stepping,); - } - - #[test] - fn step_breakpoint() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .set_breakpoint(TestSchedule, second_system) - .step_frame(); - - // since stepping stops at every system, breakpoints are ignored during - // stepping - assert_schedule_runs!(&schedule, &mut stepping, first_system); - stepping.step_frame(); - assert_schedule_runs!(&schedule, &mut stepping, second_system); - - // let's go again to verify that we wrap back around to the start of - // the frame - stepping.step_frame(); - assert_schedule_runs!(&schedule, &mut stepping, first_system); - - // should be back in a waiting state now that it ran first_system - assert_schedule_runs!(&schedule, &mut stepping,); - } - - #[test] - fn continue_breakpoint() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .set_breakpoint(TestSchedule, second_system) - .continue_frame(); - - assert_schedule_runs!(&schedule, &mut stepping, first_system); - stepping.continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, second_system); - stepping.continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, first_system); - } - - /// regression test for issue encountered while writing `system_stepping` - /// example - #[test] - fn continue_step_continue_with_breakpoint() { - let mut world = World::new(); - let mut schedule = Schedule::new(TestSchedule); - schedule.add_systems((first_system, second_system, third_system).chain()); - schedule.initialize(&mut world).unwrap(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .set_breakpoint(TestSchedule, second_system); - - stepping.continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, first_system); - - stepping.step_frame(); - assert_schedule_runs!(&schedule, &mut stepping, second_system); - - stepping.continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, third_system); - } - - #[test] - fn clear_breakpoint() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .set_breakpoint(TestSchedule, second_system) - .continue_frame(); - - assert_schedule_runs!(&schedule, &mut stepping, first_system); - stepping.continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, second_system); - - stepping.clear_breakpoint(TestSchedule, second_system); - stepping.continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - } - - #[test] - fn clear_system() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .never_run(TestSchedule, second_system) - .continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, first_system); - - stepping.clear_system(TestSchedule, second_system); - stepping.continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - } - - #[test] - fn clear_schedule() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .never_run(TestSchedule, first_system) - .never_run(TestSchedule, second_system) - .continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping,); - - stepping.clear_schedule(TestSchedule); - stepping.continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - } - - /// This was discovered in code-review, ensure that `clear_schedule` also - /// clears any pending changes too. - #[test] - fn set_behavior_then_clear_schedule() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - - stepping.never_run(TestSchedule, first_system); - stepping.clear_schedule(TestSchedule); - stepping.continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - } - - /// Ensure that if they `clear_schedule` then make further changes to the - /// schedule, those changes after the clear are applied. - #[test] - fn clear_schedule_then_set_behavior() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - - stepping.clear_schedule(TestSchedule); - stepping.never_run(TestSchedule, first_system); - stepping.continue_frame(); - assert_schedule_runs!(&schedule, &mut stepping, second_system); - } - - // Schedules such as FixedUpdate can be called multiple times in a single - // render frame. Ensure we only run steppable systems the first time the - // schedule is run - #[test] - fn multiple_calls_per_frame_continue() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestSchedule) - .enable() - .always_run(TestSchedule, second_system) - .continue_frame(); - - // start a new frame, then run the schedule two times; first system - // should only run on the first one - stepping.next_frame(); - assert_systems_run!( - &schedule, - stepping.skipped_systems(&schedule), - first_system, - second_system - ); - assert_systems_run!( - &schedule, - stepping.skipped_systems(&schedule), - second_system - ); - } - #[test] - fn multiple_calls_per_frame_step() { - let (schedule, _world) = setup(); - - let mut stepping = Stepping::new(); - stepping.add_schedule(TestSchedule).enable().step_frame(); - - // start a new frame, then run the schedule two times; first system - // should only run on the first one - stepping.next_frame(); - assert_systems_run!(&schedule, stepping.skipped_systems(&schedule), first_system); - assert_systems_run!(&schedule, stepping.skipped_systems(&schedule),); - } - - #[test] - fn step_duplicate_systems() { - let mut world = World::new(); - let mut schedule = Schedule::new(TestSchedule); - schedule.add_systems((first_system, first_system, second_system).chain()); - schedule.initialize(&mut world).unwrap(); - - let mut stepping = Stepping::new(); - stepping.add_schedule(TestSchedule).enable(); - - // needed for assert_skip_list_eq! - let system_names = vec!["first_system", "first_system", "second_system"]; - // we're going to step three times, and each system in order should run - // only once - for system_index in 0..3 { - // build the skip list by setting all bits, then clearing our the - // one system that should run this step - let mut expected = FixedBitSet::with_capacity(3); - expected.set_range(.., true); - expected.set(system_index, false); - - // step the frame and get the skip list - stepping.step_frame(); - stepping.next_frame(); - let skip_list = stepping - .skipped_systems(&schedule) - .expect("TestSchedule has been added to Stepping"); - - assert_skip_list_eq!(skip_list, expected, &system_names); - } - } - - #[test] - fn step_run_if_false() { - let mut world = World::new(); - let mut schedule = Schedule::new(TestSchedule); - - // This needs to be a system test to confirm the interaction between - // the skip list and system conditions in Schedule::run(). That means - // all of our systems need real bodies that do things. - // - // first system will be configured as `run_if(|| false)`, so it can - // just panic if called - let first_system: fn() = move || { - panic!("first_system should not be run"); - }; - - // The second system, we need to know when it has been called, so we'll - // add a resource for tracking if it has been run. The system will - // increment the run count. - #[derive(Resource)] - struct RunCount(usize); - world.insert_resource(RunCount(0)); - let second_system = |mut run_count: ResMut| { - println!("I have run!"); - run_count.0 += 1; - }; - - // build our schedule; first_system should never run, followed by - // second_system. - schedule.add_systems((first_system.run_if(|| false), second_system).chain()); - schedule.initialize(&mut world).unwrap(); - - // set up stepping - let mut stepping = Stepping::new(); - stepping.add_schedule(TestSchedule).enable(); - world.insert_resource(stepping); - - // if we step, and the run condition is false, we should not run - // second_system. The stepping cursor is at first_system, and if - // first_system wasn't able to run, that's ok. - let mut stepping = world.resource_mut::(); - stepping.step_frame(); - stepping.next_frame(); - schedule.run(&mut world); - assert_eq!( - world.resource::().0, - 0, - "second_system should not have run" - ); - - // now on the next step, second_system should run - let mut stepping = world.resource_mut::(); - stepping.step_frame(); - stepping.next_frame(); - schedule.run(&mut world); - assert_eq!( - world.resource::().0, - 1, - "second_system should have run" - ); - } - - #[test] - fn remove_schedule() { - let (schedule, _world) = setup(); - let mut stepping = Stepping::new(); - stepping.add_schedule(TestSchedule).enable(); - - // run the schedule once and verify all systems are skipped - assert_schedule_runs!(&schedule, &mut stepping,); - assert!(!stepping.schedules().unwrap().is_empty()); - - // remove the test schedule - stepping.remove_schedule(TestSchedule); - assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system); - assert!(stepping.schedules().unwrap().is_empty()); - } - - // verify that Stepping can construct an ordered list of schedules - #[test] - fn schedules() { - let mut world = World::new(); - - // build & initialize a few schedules - let mut schedule_a = Schedule::new(TestScheduleA); - schedule_a.initialize(&mut world).unwrap(); - let mut schedule_b = Schedule::new(TestScheduleB); - schedule_b.initialize(&mut world).unwrap(); - let mut schedule_c = Schedule::new(TestScheduleC); - schedule_c.initialize(&mut world).unwrap(); - let mut schedule_d = Schedule::new(TestScheduleD); - schedule_d.initialize(&mut world).unwrap(); - - // setup stepping and add all the schedules - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestScheduleA) - .add_schedule(TestScheduleB) - .add_schedule(TestScheduleC) - .add_schedule(TestScheduleD) - .enable() - .next_frame(); - - assert!(stepping.schedules().is_err()); - - stepping.skipped_systems(&schedule_b); - assert!(stepping.schedules().is_err()); - stepping.skipped_systems(&schedule_a); - assert!(stepping.schedules().is_err()); - stepping.skipped_systems(&schedule_c); - assert!(stepping.schedules().is_err()); - - // when we call the last schedule, Stepping should have enough data to - // return an ordered list of schedules - stepping.skipped_systems(&schedule_d); - assert!(stepping.schedules().is_ok()); - - assert_eq!( - *stepping.schedules().unwrap(), - vec![ - TestScheduleB.intern(), - TestScheduleA.intern(), - TestScheduleC.intern(), - TestScheduleD.intern(), - ] - ); - } - - #[test] - fn verify_cursor() { - // helper to build a cursor tuple for the supplied schedule - fn cursor(schedule: &Schedule, index: usize) -> (InternedScheduleLabel, NodeId) { - let node_id = schedule.executable().system_ids[index]; - (schedule.label(), NodeId::System(node_id)) - } - - let mut world = World::new(); - let mut slotmap = SlotMap::::with_key(); - - // create two schedules with a number of systems in them - let mut schedule_a = Schedule::new(TestScheduleA); - schedule_a.add_systems((|| {}, || {}, || {}, || {}).chain()); - schedule_a.initialize(&mut world).unwrap(); - let mut schedule_b = Schedule::new(TestScheduleB); - schedule_b.add_systems((|| {}, || {}, || {}, || {}).chain()); - schedule_b.initialize(&mut world).unwrap(); - - // setup stepping and add all schedules - let mut stepping = Stepping::new(); - stepping - .add_schedule(TestScheduleA) - .add_schedule(TestScheduleB) - .enable(); - - assert!(stepping.cursor().is_none()); - - // step the system nine times, and verify the cursor before & after - // each step - let mut cursors = Vec::new(); - for _ in 0..9 { - stepping.step_frame().next_frame(); - cursors.push(stepping.cursor()); - stepping.skipped_systems(&schedule_a); - stepping.skipped_systems(&schedule_b); - cursors.push(stepping.cursor()); - } - - #[rustfmt::skip] - assert_eq!( - cursors, - vec![ - // before render frame // after render frame - None, Some(cursor(&schedule_a, 1)), - Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)), - Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_a, 3)), - Some(cursor(&schedule_a, 3)), Some(cursor(&schedule_b, 0)), - Some(cursor(&schedule_b, 0)), Some(cursor(&schedule_b, 1)), - Some(cursor(&schedule_b, 1)), Some(cursor(&schedule_b, 2)), - Some(cursor(&schedule_b, 2)), Some(cursor(&schedule_b, 3)), - Some(cursor(&schedule_b, 3)), None, - Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)), - ] - ); - - let sys0 = slotmap.insert(()); - let sys1 = slotmap.insert(()); - let _sys2 = slotmap.insert(()); - let sys3 = slotmap.insert(()); - - // reset our cursor (disable/enable), and update stepping to test if the - // cursor properly skips over AlwaysRun & NeverRun systems. Also set - // a Break system to ensure that shows properly in the cursor - stepping - // disable/enable to reset cursor - .disable() - .enable() - .set_breakpoint_node(TestScheduleA, NodeId::System(sys1)) - .always_run_node(TestScheduleA, NodeId::System(sys3)) - .never_run_node(TestScheduleB, NodeId::System(sys0)); - - let mut cursors = Vec::new(); - for _ in 0..9 { - stepping.step_frame().next_frame(); - cursors.push(stepping.cursor()); - stepping.skipped_systems(&schedule_a); - stepping.skipped_systems(&schedule_b); - cursors.push(stepping.cursor()); - } - - #[rustfmt::skip] - assert_eq!( - cursors, - vec![ - // before render frame // after render frame - Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)), - Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)), - Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_b, 1)), - Some(cursor(&schedule_b, 1)), Some(cursor(&schedule_b, 2)), - Some(cursor(&schedule_b, 2)), Some(cursor(&schedule_b, 3)), - Some(cursor(&schedule_b, 3)), None, - Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)), - Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)), - Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_b, 1)), - ] - ); - } -} diff --git a/src/scheduler.rs b/src/scheduler.rs index f9f25fd..1770611 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -11,11 +11,16 @@ use soroban_sdk::Env; /// Works with the full `World` type and boxed `System` trait objects. /// /// # Example -/// ```ignore +/// ``` +/// use cougr_core::scheduler::SystemScheduler; +/// use cougr_core::system::MovementSystem; +/// use cougr_core::world::World; +/// /// let mut scheduler = SystemScheduler::new(); /// scheduler.add_system(MovementSystem); -/// scheduler.add_system(CollisionSystem); +/// let mut world = World::new(); /// scheduler.run_all(&mut world); +/// assert_eq!(scheduler.system_count(), 1); /// ``` pub struct SystemScheduler { systems: Vec<(String, Box>)>, @@ -74,14 +79,27 @@ impl Default for SystemScheduler { /// are standalone functions taking `(&mut SimpleWorld, &Env)`. /// /// # Example -/// ```ignore -/// fn physics_system(world: &mut SimpleWorld, env: &Env) { /* ... */ } -/// fn scoring_system(world: &mut SimpleWorld, env: &Env) { /* ... */ } +/// ``` +/// use cougr_core::scheduler::SimpleScheduler; +/// use cougr_core::simple_world::SimpleWorld; +/// use soroban_sdk::{Bytes, Env}; +/// +/// fn physics_system(world: &mut SimpleWorld, env: &Env) { +/// let entity = world.spawn_entity(); +/// world.add_component(entity, soroban_sdk::Symbol::new(env, "physics"), Bytes::new(env)); +/// } +/// fn scoring_system(world: &mut SimpleWorld, env: &Env) { +/// let entity = world.spawn_entity(); +/// world.add_component(entity, soroban_sdk::Symbol::new(env, "scoring"), Bytes::new(env)); +/// } /// +/// let env = Env::default(); +/// let mut world = SimpleWorld::new(&env); /// let mut scheduler = SimpleScheduler::new(); /// scheduler.add_system("physics", physics_system); /// scheduler.add_system("scoring", scoring_system); /// scheduler.run_all(&mut world, &env); +/// assert_eq!(scheduler.system_count(), 2); /// ``` pub struct SimpleScheduler { systems: Vec, diff --git a/src/simple_world.rs b/src/simple_world.rs index 2c4cc18..45412c9 100644 --- a/src/simple_world.rs +++ b/src/simple_world.rs @@ -20,23 +20,33 @@ pub type EntityId = u32; /// Both maps are transparent to `get_component()`, `has_component()`, and `remove_component()`. /// /// # Example -/// ```ignore +/// ``` +/// use cougr_core::component::ComponentStorage; +/// use cougr_core::simple_world::SimpleWorld; +/// use soroban_sdk::{symbol_short, Bytes, Env}; +/// +/// let env = Env::default(); /// let mut world = SimpleWorld::new(&env); /// let entity_id = world.spawn_entity(); -/// world.add_component(entity_id, symbol_short!("position"), pos.serialize(&env)); -/// // Sparse components use a dedicated method: -/// world.add_component_with_storage(entity_id, symbol_short!("marker"), data, ComponentStorage::Sparse); +/// world.add_component(entity_id, symbol_short!("position"), Bytes::new(&env)); +/// world.add_component_with_storage( +/// entity_id, +/// symbol_short!("marker"), +/// Bytes::new(&env), +/// ComponentStorage::Sparse, +/// ); +/// assert!(world.has_component(entity_id, &symbol_short!("position"))); /// ``` #[contracttype] #[derive(Clone, Debug)] pub struct SimpleWorld { - pub next_entity_id: EntityId, + pub next_entity_id: u32, /// Table component data keyed by (entity_id, component_type). - pub components: Map<(EntityId, Symbol), Bytes>, + pub components: Map<(u32, Symbol), Bytes>, /// Sparse component data keyed by (entity_id, component_type). - pub sparse_components: Map<(EntityId, Symbol), Bytes>, + pub sparse_components: Map<(u32, Symbol), Bytes>, /// Tracks which component types each entity has. - pub entity_components: Map>, + pub entity_components: Map>, /// Version counter incremented on structural changes (add/remove/despawn). /// Used for query cache invalidation. pub version: u64, @@ -223,8 +233,17 @@ impl SimpleWorld { /// Get a component and deserialize it into the concrete type. /// /// # Example - /// ```ignore + /// ``` + /// use cougr_core::component::Position; + /// use cougr_core::simple_world::SimpleWorld; + /// use soroban_sdk::Env; + /// + /// let env = Env::default(); + /// let mut world = SimpleWorld::new(&env); + /// let entity_id = world.spawn_entity(); + /// world.set_typed(&env, entity_id, &Position::new(10, 20)); /// let pos: Option = world.get_typed::(&env, entity_id); + /// assert_eq!(pos.unwrap().x, 10); /// ``` pub fn get_typed(&self, env: &Env, entity_id: EntityId) -> Option { let bytes = self.get_component(entity_id, &T::component_type())?; @@ -234,8 +253,16 @@ impl SimpleWorld { /// Serialize a component and store it, using the type's default storage kind. /// /// # Example - /// ```ignore + /// ``` + /// use cougr_core::component::Position; + /// use cougr_core::simple_world::SimpleWorld; + /// use soroban_sdk::Env; + /// + /// let env = Env::default(); + /// let mut world = SimpleWorld::new(&env); + /// let entity_id = world.spawn_entity(); /// world.set_typed(&env, entity_id, &Position::new(10, 20)); + /// assert!(world.has_typed::(entity_id)); /// ``` pub fn set_typed(&mut self, env: &Env, entity_id: EntityId, component: &T) { let symbol = T::component_type(); diff --git a/src/standards/access_control.rs b/src/standards/access_control.rs new file mode 100644 index 0000000..ec75a6a --- /dev/null +++ b/src/standards/access_control.rs @@ -0,0 +1,213 @@ +use soroban_sdk::{contracttype, Address, Env, Symbol}; + +use super::error::StandardsError; + +const ROLE_MEMBER_PREFIX: &str = "std_role_m"; +const ROLE_ADMIN_PREFIX: &str = "std_role_a"; + +pub const DEFAULT_ADMIN_ROLE_NAME: &str = "DEFAULT_ADMIN_ROLE"; + +/// Role-based access control with per-role admin delegation. +#[derive(Clone, Debug)] +pub struct AccessControl { + id: Symbol, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RoleGrantedEvent { + pub role: Symbol, + pub account: Address, + pub sender: Address, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RoleRevokedEvent { + pub role: Symbol, + pub account: Address, + pub sender: Address, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RoleAdminChangedEvent { + pub role: Symbol, + pub previous_admin_role: Symbol, + pub new_admin_role: Symbol, + pub sender: Address, +} + +impl AccessControl { + pub fn new(id: Symbol) -> Self { + Self { id } + } + + pub fn initialize( + &self, + env: &Env, + admin: &Address, + ) -> Result { + let default_admin_role = self.default_admin_role(env); + let admin_key = self.role_admin_key(env, &default_admin_role); + if env.storage().persistent().has(&admin_key) { + return Err(StandardsError::AlreadyInitialized); + } + + env.storage() + .persistent() + .set(&admin_key, &default_admin_role); + self.grant_role_unchecked(env, &default_admin_role, admin); + + Ok(RoleGrantedEvent { + role: default_admin_role, + account: admin.clone(), + sender: admin.clone(), + }) + } + + pub fn default_admin_role(&self, env: &Env) -> Symbol { + Symbol::new(env, DEFAULT_ADMIN_ROLE_NAME) + } + + pub fn has_role(&self, env: &Env, role: &Symbol, account: &Address) -> bool { + env.storage() + .persistent() + .has(&self.role_member_key(env, role, account)) + } + + pub fn require_role( + &self, + env: &Env, + role: &Symbol, + account: &Address, + ) -> Result<(), StandardsError> { + if self.has_role(env, role, account) { + return Ok(()); + } + Err(StandardsError::Unauthorized) + } + + pub fn role_admin(&self, env: &Env, role: &Symbol) -> Symbol { + env.storage() + .persistent() + .get(&self.role_admin_key(env, role)) + .unwrap_or_else(|| self.default_admin_role(env)) + } + + pub fn grant_role( + &self, + env: &Env, + caller: &Address, + role: &Symbol, + account: &Address, + ) -> Result { + let admin_role = self.role_admin(env, role); + self.require_role(env, &admin_role, caller)?; + if self.has_role(env, role, account) { + return Err(StandardsError::RoleAlreadyGranted); + } + + self.grant_role_unchecked(env, role, account); + Ok(RoleGrantedEvent { + role: role.clone(), + account: account.clone(), + sender: caller.clone(), + }) + } + + pub fn revoke_role( + &self, + env: &Env, + caller: &Address, + role: &Symbol, + account: &Address, + ) -> Result { + let admin_role = self.role_admin(env, role); + self.require_role(env, &admin_role, caller)?; + if !self.has_role(env, role, account) { + return Err(StandardsError::RoleNotGranted); + } + + env.storage() + .persistent() + .remove(&self.role_member_key(env, role, account)); + + Ok(RoleRevokedEvent { + role: role.clone(), + account: account.clone(), + sender: caller.clone(), + }) + } + + pub fn renounce_role( + &self, + env: &Env, + role: &Symbol, + caller: &Address, + ) -> Result { + if !self.has_role(env, role, caller) { + return Err(StandardsError::RoleNotGranted); + } + + env.storage() + .persistent() + .remove(&self.role_member_key(env, role, caller)); + + Ok(RoleRevokedEvent { + role: role.clone(), + account: caller.clone(), + sender: caller.clone(), + }) + } + + pub fn set_role_admin( + &self, + env: &Env, + caller: &Address, + role: &Symbol, + new_admin_role: &Symbol, + ) -> Result { + let previous_admin_role = self.role_admin(env, role); + self.require_role(env, &previous_admin_role, caller)?; + + env.storage() + .persistent() + .set(&self.role_admin_key(env, role), new_admin_role); + + Ok(RoleAdminChangedEvent { + role: role.clone(), + previous_admin_role, + new_admin_role: new_admin_role.clone(), + sender: caller.clone(), + }) + } + + fn grant_role_unchecked(&self, env: &Env, role: &Symbol, account: &Address) { + env.storage() + .persistent() + .set(&self.role_member_key(env, role, account), &true); + } + + fn role_member_key( + &self, + env: &Env, + role: &Symbol, + account: &Address, + ) -> (Symbol, Symbol, Symbol, Address) { + ( + Symbol::new(env, ROLE_MEMBER_PREFIX), + self.id.clone(), + role.clone(), + account.clone(), + ) + } + + fn role_admin_key(&self, env: &Env, role: &Symbol) -> (Symbol, Symbol, Symbol) { + ( + Symbol::new(env, ROLE_ADMIN_PREFIX), + self.id.clone(), + role.clone(), + ) + } +} diff --git a/src/standards/batch.rs b/src/standards/batch.rs new file mode 100644 index 0000000..d0d5e1c --- /dev/null +++ b/src/standards/batch.rs @@ -0,0 +1,43 @@ +use alloc::vec::Vec; + +use super::error::StandardsError; + +/// Generic batch validator and executor for standards-layer flows. +#[derive(Clone, Debug)] +pub struct BatchExecutor { + max_size: usize, +} + +impl BatchExecutor { + pub fn new(max_size: usize) -> Self { + Self { max_size } + } + + pub fn max_size(&self) -> usize { + self.max_size + } + + pub fn validate_len(&self, len: usize) -> Result<(), StandardsError> { + if len == 0 { + return Err(StandardsError::BatchEmpty); + } + if len > self.max_size { + return Err(StandardsError::BatchTooLarge); + } + Ok(()) + } + + pub fn execute( + &self, + items: &[T], + mut executor: impl FnMut(&T) -> Result, + ) -> Result, StandardsError> { + self.validate_len(items.len())?; + + let mut results = Vec::with_capacity(items.len()); + for item in items { + results.push(executor(item)?); + } + Ok(results) + } +} diff --git a/src/standards/delayed_execution.rs b/src/standards/delayed_execution.rs new file mode 100644 index 0000000..3736dac --- /dev/null +++ b/src/standards/delayed_execution.rs @@ -0,0 +1,221 @@ +use soroban_sdk::{contracttype, Bytes, Env, Symbol, Vec}; + +use super::error::StandardsError; + +const OPERATION_PREFIX: &str = "std_delay"; +const OPERATION_IDS_PREFIX: &str = "std_delay_ids"; +const NONCE_PREFIX: &str = "std_delay_n"; + +/// Storage-backed delayed execution queue. +#[derive(Clone, Debug)] +pub struct DelayedExecutionPolicy { + id: Symbol, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DelayedOperation { + pub operation_id: u64, + pub action: Symbol, + pub payload: Bytes, + pub scheduled_at: u64, + pub not_before: u64, + pub expires_at: u64, + pub executed: bool, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DelayedExecutionScheduledEvent { + pub operation_id: u64, + pub action: Symbol, + pub not_before: u64, + pub expires_at: u64, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DelayedExecutionCancelledEvent { + pub operation_id: u64, + pub action: Symbol, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DelayedExecutionExecutedEvent { + pub operation_id: u64, + pub action: Symbol, + pub executed_at: u64, +} + +impl DelayedExecutionPolicy { + pub fn new(id: Symbol) -> Self { + Self { id } + } + + pub fn schedule( + &self, + env: &Env, + action: Symbol, + payload: Bytes, + delay: u64, + ttl: u64, + ) -> Result { + let now = env.ledger().timestamp(); + let operation_id = self.next_operation_id(env); + let operation = DelayedOperation { + operation_id, + action: action.clone(), + payload, + scheduled_at: now, + not_before: now + delay, + expires_at: now + delay + ttl, + executed: false, + }; + + env.storage() + .persistent() + .set(&self.operation_key(env, operation_id), &operation); + + let mut ids = self.operation_ids(env); + ids.push_back(operation_id); + env.storage() + .persistent() + .set(&self.operation_ids_key(env), &ids); + + Ok(DelayedExecutionScheduledEvent { + operation_id, + action, + not_before: operation.not_before, + expires_at: operation.expires_at, + }) + } + + pub fn operation(&self, env: &Env, operation_id: u64) -> Option { + env.storage() + .persistent() + .get(&self.operation_key(env, operation_id)) + } + + pub fn pending_operations(&self, env: &Env) -> Vec { + let ids = self.operation_ids(env); + let mut operations = Vec::new(env); + + for i in 0..ids.len() { + if let Some(operation_id) = ids.get(i) { + if let Some(operation) = self.operation(env, operation_id) { + if !operation.executed { + operations.push_back(operation); + } + } + } + } + + operations + } + + pub fn cancel( + &self, + env: &Env, + operation_id: u64, + ) -> Result { + let operation = self + .operation(env, operation_id) + .ok_or(StandardsError::OperationNotFound)?; + + self.remove_operation(env, operation_id); + + Ok(DelayedExecutionCancelledEvent { + operation_id, + action: operation.action, + }) + } + + pub fn execute_ready( + &self, + env: &Env, + operation_id: u64, + ) -> Result { + let mut operation = self + .operation(env, operation_id) + .ok_or(StandardsError::OperationNotFound)?; + + if operation.executed { + return Err(StandardsError::OperationAlreadyExecuted); + } + + let now = env.ledger().timestamp(); + if now < operation.not_before { + return Err(StandardsError::OperationNotReady); + } + if now > operation.expires_at { + return Err(StandardsError::OperationExpired); + } + + operation.executed = true; + env.storage() + .persistent() + .set(&self.operation_key(env, operation_id), &operation); + self.remove_operation_id(env, operation_id); + + Ok(DelayedExecutionExecutedEvent { + operation_id, + action: operation.action, + executed_at: now, + }) + } + + fn next_operation_id(&self, env: &Env) -> u64 { + let key = self.nonce_key(env); + let next = env.storage().persistent().get::<_, u64>(&key).unwrap_or(0) + 1; + env.storage().persistent().set(&key, &next); + next + } + + fn remove_operation(&self, env: &Env, operation_id: u64) { + env.storage() + .persistent() + .remove(&self.operation_key(env, operation_id)); + self.remove_operation_id(env, operation_id); + } + + fn remove_operation_id(&self, env: &Env, operation_id: u64) { + let ids = self.operation_ids(env); + let mut retained = Vec::new(env); + + for i in 0..ids.len() { + if let Some(candidate) = ids.get(i) { + if candidate != operation_id { + retained.push_back(candidate); + } + } + } + + env.storage() + .persistent() + .set(&self.operation_ids_key(env), &retained); + } + + fn operation_ids(&self, env: &Env) -> Vec { + env.storage() + .persistent() + .get(&self.operation_ids_key(env)) + .unwrap_or_else(|| Vec::new(env)) + } + + fn operation_key(&self, env: &Env, operation_id: u64) -> (Symbol, Symbol, u64) { + ( + Symbol::new(env, OPERATION_PREFIX), + self.id.clone(), + operation_id, + ) + } + + fn operation_ids_key(&self, env: &Env) -> (Symbol, Symbol) { + (Symbol::new(env, OPERATION_IDS_PREFIX), self.id.clone()) + } + + fn nonce_key(&self, env: &Env) -> (Symbol, Symbol) { + (Symbol::new(env, NONCE_PREFIX), self.id.clone()) + } +} diff --git a/src/standards/error.rs b/src/standards/error.rs new file mode 100644 index 0000000..c07ebc3 --- /dev/null +++ b/src/standards/error.rs @@ -0,0 +1,28 @@ +use soroban_sdk::contracterror; + +/// Errors returned by the reusable standards layer. +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u32)] +pub enum StandardsError { + Unauthorized = 60, + AlreadyInitialized = 61, + OwnerNotSet = 62, + PendingOwnerNotSet = 63, + PendingOwnerMismatch = 64, + RoleAlreadyGranted = 65, + RoleNotGranted = 66, + MissingRoleAdmin = 67, + Paused = 68, + NotPaused = 69, + ExecutionLocked = 70, + RecoveryActive = 71, + RecoveryInactive = 72, + BatchEmpty = 73, + BatchTooLarge = 74, + OperationNotReady = 75, + OperationExpired = 76, + OperationNotFound = 77, + OperationAlreadyExecuted = 78, + ExecutionNotLocked = 79, +} diff --git a/src/standards/execution_guard.rs b/src/standards/execution_guard.rs new file mode 100644 index 0000000..a5e57e4 --- /dev/null +++ b/src/standards/execution_guard.rs @@ -0,0 +1,72 @@ +use soroban_sdk::{contracttype, Env, Symbol}; + +use super::error::StandardsError; + +const EXECUTION_LOCK_PREFIX: &str = "std_exec"; + +/// Storage-backed execution lock for sensitive state transitions. +#[derive(Clone, Debug)] +pub struct ExecutionGuard { + id: Symbol, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ExecutionGuardEnteredEvent { + pub guard_id: Symbol, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ExecutionGuardExitedEvent { + pub guard_id: Symbol, +} + +impl ExecutionGuard { + pub fn new(id: Symbol) -> Self { + Self { id } + } + + pub fn is_locked(&self, env: &Env) -> bool { + env.storage() + .persistent() + .get(&self.lock_key(env)) + .unwrap_or(false) + } + + pub fn require_unlocked(&self, env: &Env) -> Result<(), StandardsError> { + if self.is_locked(env) { + return Err(StandardsError::ExecutionLocked); + } + Ok(()) + } + + pub fn enter(&self, env: &Env) -> Result { + self.require_unlocked(env)?; + env.storage().persistent().set(&self.lock_key(env), &true); + Ok(ExecutionGuardEnteredEvent { + guard_id: self.id.clone(), + }) + } + + pub fn exit(&self, env: &Env) -> Result { + if !self.is_locked(env) { + return Err(StandardsError::ExecutionNotLocked); + } + env.storage().persistent().set(&self.lock_key(env), &false); + Ok(ExecutionGuardExitedEvent { + guard_id: self.id.clone(), + }) + } + + pub fn execute(&self, env: &Env, f: impl FnOnce() -> T) -> Result { + self.enter(env)?; + let result = f(); + self.exit(env)?; + Ok(result) + } + + fn lock_key(&self, env: &Env) -> (Symbol, Symbol) { + (Symbol::new(env, EXECUTION_LOCK_PREFIX), self.id.clone()) + } +} diff --git a/src/standards/mod.rs b/src/standards/mod.rs new file mode 100644 index 0000000..b7ef39d --- /dev/null +++ b/src/standards/mod.rs @@ -0,0 +1,33 @@ +//! Reusable contract standards for Cougr integrations. +//! +//! The standards layer is intentionally framework-oriented rather than +//! example-specific. Each standard is storage-backed where persistence matters, +//! exposes typed state-transition events, and keeps authorization checks +//! explicit instead of hidden behind implicit caller assumptions. + +mod access_control; +mod batch; +mod delayed_execution; +mod error; +mod execution_guard; +mod ownable; +mod pausable; +mod recovery_guard; + +pub use access_control::{ + AccessControl, RoleAdminChangedEvent, RoleGrantedEvent, RoleRevokedEvent, + DEFAULT_ADMIN_ROLE_NAME, +}; +pub use batch::BatchExecutor; +pub use delayed_execution::{ + DelayedExecutionCancelledEvent, DelayedExecutionExecutedEvent, DelayedExecutionPolicy, + DelayedExecutionScheduledEvent, DelayedOperation, +}; +pub use error::StandardsError; +pub use execution_guard::{ExecutionGuard, ExecutionGuardEnteredEvent, ExecutionGuardExitedEvent}; +pub use ownable::{ + Ownable, Ownable2Step, OwnershipTransferCancelledEvent, OwnershipTransferStartedEvent, + OwnershipTransferredEvent, +}; +pub use pausable::{Pausable, PausedEvent, UnpausedEvent}; +pub use recovery_guard::{RecoveryGuard, RecoveryGuardActivatedEvent, RecoveryGuardClearedEvent}; diff --git a/src/standards/ownable.rs b/src/standards/ownable.rs new file mode 100644 index 0000000..1fb7c16 --- /dev/null +++ b/src/standards/ownable.rs @@ -0,0 +1,218 @@ +use soroban_sdk::{contracttype, Address, Env, Symbol}; + +use super::error::StandardsError; + +const OWNER_PREFIX: &str = "std_owner"; +const PENDING_OWNER_PREFIX: &str = "std_powner"; + +/// OpenZeppelin-style single-owner control primitive. +#[derive(Clone, Debug)] +pub struct Ownable { + id: Symbol, +} + +/// Two-step ownership handoff built on top of [`Ownable`]. +#[derive(Clone, Debug)] +pub struct Ownable2Step { + inner: Ownable, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct OwnershipTransferredEvent { + pub previous_owner: Option
, + pub new_owner: Option
, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct OwnershipTransferStartedEvent { + pub owner: Address, + pub pending_owner: Address, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct OwnershipTransferCancelledEvent { + pub owner: Address, + pub pending_owner: Address, +} + +impl Ownable { + pub fn new(id: Symbol) -> Self { + Self { id } + } + + pub fn initialize( + &self, + env: &Env, + owner: &Address, + ) -> Result { + if self.owner(env).is_some() { + return Err(StandardsError::AlreadyInitialized); + } + + env.storage().persistent().set(&self.owner_key(env), owner); + env.storage() + .persistent() + .remove(&self.pending_owner_key(env)); + + Ok(OwnershipTransferredEvent { + previous_owner: None, + new_owner: Some(owner.clone()), + }) + } + + pub fn owner(&self, env: &Env) -> Option
{ + env.storage().persistent().get(&self.owner_key(env)) + } + + pub fn pending_owner(&self, env: &Env) -> Option
{ + env.storage().persistent().get(&self.pending_owner_key(env)) + } + + pub fn require_owner(&self, env: &Env, caller: &Address) -> Result<(), StandardsError> { + let owner = self.owner(env).ok_or(StandardsError::OwnerNotSet)?; + if owner != *caller { + return Err(StandardsError::Unauthorized); + } + Ok(()) + } + + pub fn transfer_ownership( + &self, + env: &Env, + caller: &Address, + new_owner: &Address, + ) -> Result { + self.require_owner(env, caller)?; + let previous_owner = self.owner(env); + env.storage() + .persistent() + .set(&self.owner_key(env), new_owner); + env.storage() + .persistent() + .remove(&self.pending_owner_key(env)); + + Ok(OwnershipTransferredEvent { + previous_owner, + new_owner: Some(new_owner.clone()), + }) + } + + pub fn renounce_ownership( + &self, + env: &Env, + caller: &Address, + ) -> Result { + self.require_owner(env, caller)?; + let previous_owner = self.owner(env); + env.storage().persistent().remove(&self.owner_key(env)); + env.storage() + .persistent() + .remove(&self.pending_owner_key(env)); + + Ok(OwnershipTransferredEvent { + previous_owner, + new_owner: None, + }) + } + + fn owner_key(&self, env: &Env) -> (Symbol, Symbol) { + (Symbol::new(env, OWNER_PREFIX), self.id.clone()) + } + + fn pending_owner_key(&self, env: &Env) -> (Symbol, Symbol) { + (Symbol::new(env, PENDING_OWNER_PREFIX), self.id.clone()) + } +} + +impl Ownable2Step { + pub fn new(id: Symbol) -> Self { + Self { + inner: Ownable::new(id), + } + } + + pub fn initialize( + &self, + env: &Env, + owner: &Address, + ) -> Result { + self.inner.initialize(env, owner) + } + + pub fn owner(&self, env: &Env) -> Option
{ + self.inner.owner(env) + } + + pub fn pending_owner(&self, env: &Env) -> Option
{ + self.inner.pending_owner(env) + } + + pub fn require_owner(&self, env: &Env, caller: &Address) -> Result<(), StandardsError> { + self.inner.require_owner(env, caller) + } + + pub fn begin_transfer( + &self, + env: &Env, + caller: &Address, + pending_owner: &Address, + ) -> Result { + self.inner.require_owner(env, caller)?; + env.storage() + .persistent() + .set(&self.inner.pending_owner_key(env), pending_owner); + + Ok(OwnershipTransferStartedEvent { + owner: caller.clone(), + pending_owner: pending_owner.clone(), + }) + } + + pub fn accept_transfer( + &self, + env: &Env, + caller: &Address, + ) -> Result { + let pending_owner = self + .pending_owner(env) + .ok_or(StandardsError::PendingOwnerNotSet)?; + if pending_owner != *caller { + return Err(StandardsError::PendingOwnerMismatch); + } + + let previous_owner = self.owner(env); + env.storage() + .persistent() + .set(&self.inner.owner_key(env), caller); + env.storage() + .persistent() + .remove(&self.inner.pending_owner_key(env)); + + Ok(OwnershipTransferredEvent { + previous_owner, + new_owner: Some(caller.clone()), + }) + } + + pub fn cancel_transfer( + &self, + env: &Env, + caller: &Address, + ) -> Result { + self.inner.require_owner(env, caller)?; + let pending_owner = self + .pending_owner(env) + .ok_or(StandardsError::PendingOwnerNotSet)?; + env.storage() + .persistent() + .remove(&self.inner.pending_owner_key(env)); + + Ok(OwnershipTransferCancelledEvent { + owner: caller.clone(), + pending_owner, + }) + } +} diff --git a/src/standards/pausable.rs b/src/standards/pausable.rs new file mode 100644 index 0000000..57c3cf5 --- /dev/null +++ b/src/standards/pausable.rs @@ -0,0 +1,76 @@ +use soroban_sdk::{contracttype, Address, Env, Symbol}; + +use super::error::StandardsError; + +const PAUSED_PREFIX: &str = "std_pause"; + +/// Pause state primitive for emergency stops. +#[derive(Clone, Debug)] +pub struct Pausable { + id: Symbol, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PausedEvent { + pub account: Address, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UnpausedEvent { + pub account: Address, +} + +impl Pausable { + pub fn new(id: Symbol) -> Self { + Self { id } + } + + pub fn is_paused(&self, env: &Env) -> bool { + env.storage() + .persistent() + .get(&self.paused_key(env)) + .unwrap_or(false) + } + + pub fn require_paused(&self, env: &Env) -> Result<(), StandardsError> { + if self.is_paused(env) { + return Ok(()); + } + Err(StandardsError::NotPaused) + } + + pub fn require_not_paused(&self, env: &Env) -> Result<(), StandardsError> { + if self.is_paused(env) { + return Err(StandardsError::Paused); + } + Ok(()) + } + + pub fn pause(&self, env: &Env, caller: &Address) -> Result { + if self.is_paused(env) { + return Err(StandardsError::Paused); + } + env.storage().persistent().set(&self.paused_key(env), &true); + Ok(PausedEvent { + account: caller.clone(), + }) + } + + pub fn unpause(&self, env: &Env, caller: &Address) -> Result { + if !self.is_paused(env) { + return Err(StandardsError::NotPaused); + } + env.storage() + .persistent() + .set(&self.paused_key(env), &false); + Ok(UnpausedEvent { + account: caller.clone(), + }) + } + + fn paused_key(&self, env: &Env) -> (Symbol, Symbol) { + (Symbol::new(env, PAUSED_PREFIX), self.id.clone()) + } +} diff --git a/src/standards/recovery_guard.rs b/src/standards/recovery_guard.rs new file mode 100644 index 0000000..abe94a5 --- /dev/null +++ b/src/standards/recovery_guard.rs @@ -0,0 +1,82 @@ +use soroban_sdk::{contracttype, Address, Env, Symbol}; + +use super::error::StandardsError; + +const RECOVERY_GUARD_PREFIX: &str = "std_recov"; + +/// Guard used to block sensitive flows while recovery is active. +#[derive(Clone, Debug)] +pub struct RecoveryGuard { + id: Symbol, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RecoveryGuardActivatedEvent { + pub account: Address, + pub activated_at: u64, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RecoveryGuardClearedEvent { + pub account: Address, + pub cleared_at: u64, +} + +impl RecoveryGuard { + pub fn new(id: Symbol) -> Self { + Self { id } + } + + pub fn is_active(&self, env: &Env) -> bool { + env.storage() + .persistent() + .get::<_, bool>(&self.guard_key(env)) + .unwrap_or(false) + } + + pub fn require_active(&self, env: &Env) -> Result<(), StandardsError> { + if self.is_active(env) { + return Ok(()); + } + Err(StandardsError::RecoveryInactive) + } + + pub fn require_inactive(&self, env: &Env) -> Result<(), StandardsError> { + if self.is_active(env) { + return Err(StandardsError::RecoveryActive); + } + Ok(()) + } + + pub fn activate( + &self, + env: &Env, + caller: &Address, + ) -> Result { + self.require_inactive(env)?; + env.storage().persistent().set(&self.guard_key(env), &true); + Ok(RecoveryGuardActivatedEvent { + account: caller.clone(), + activated_at: env.ledger().timestamp(), + }) + } + + pub fn clear( + &self, + env: &Env, + caller: &Address, + ) -> Result { + self.require_active(env)?; + env.storage().persistent().set(&self.guard_key(env), &false); + Ok(RecoveryGuardClearedEvent { + account: caller.clone(), + cleared_at: env.ledger().timestamp(), + }) + } + + fn guard_key(&self, env: &Env) -> (Symbol, Symbol) { + (Symbol::new(env, RECOVERY_GUARD_PREFIX), self.id.clone()) + } +} diff --git a/src/system.rs b/src/system.rs index 62df255..67ecfc1 100644 --- a/src/system.rs +++ b/src/system.rs @@ -28,12 +28,14 @@ pub trait SystemParam { fn fetch_mut(world: &mut World) -> Self::Fetch; } -/// A query for entities with specific components -pub struct Query { +/// Internal query helper used by the legacy system examples in this module. +#[allow(dead_code)] +struct LegacySystemQuery { component_types: Vec, } -impl Query { +#[allow(dead_code)] +impl LegacySystemQuery { /// Create a new query pub fn new(component_types: Vec) -> Self { Self { component_types } @@ -69,15 +71,17 @@ impl Query { } } -/// Query state for tracking query results -pub struct QueryState { - query: Query, +/// Internal cached query state for the legacy system examples in this module. +#[allow(dead_code)] +struct LegacySystemQueryState { + query: LegacySystemQuery, last_results: Vec, } -impl QueryState { +#[allow(dead_code)] +impl LegacySystemQueryState { /// Create a new query state - pub fn new(query: Query) -> Self { + pub fn new(query: LegacySystemQuery) -> Self { let env = soroban_sdk::Env::default(); Self { query, @@ -160,14 +164,14 @@ where /// System parameter for querying entities pub struct QueryParam { - query: Query, + query: LegacySystemQuery, } impl QueryParam { /// Create a new query parameter pub fn new(component_types: Vec) -> Self { Self { - query: Query::new(component_types), + query: LegacySystemQuery::new(component_types), } } @@ -312,7 +316,7 @@ mod tests { let mut component_types = Vec::new(&env); component_types.push_back(symbol_short!("position")); component_types.push_back(symbol_short!("velocity")); - let query = Query::new(component_types); + let query = LegacySystemQuery::new(component_types); let world = World::new(); let results = query.execute(&world); @@ -324,8 +328,8 @@ mod tests { let env = Env::default(); let mut component_types = Vec::new(&env); component_types.push_back(symbol_short!("position")); - let query = Query::new(component_types); - let mut query_state = QueryState::new(query); + let query = LegacySystemQuery::new(component_types); + let mut query_state = LegacySystemQueryState::new(query); let world = World::new(); let results = query_state.execute(&world); diff --git a/src/system/adapter_system.rs b/src/system/adapter_system.rs deleted file mode 100644 index 6b13348..0000000 --- a/src/system/adapter_system.rs +++ /dev/null @@ -1,221 +0,0 @@ -use alloc::vec::Vec; -use bevy_utils::prelude::DebugName; - -use super::{IntoSystem, ReadOnlySystem, RunSystemError, System, SystemParamValidationError}; -use crate::{ - schedule::InternedSystemSet, - system::{input::SystemInput, SystemIn}, - world::unsafe_world_cell::UnsafeWorldCell, -}; - -/// Customizes the behavior of an [`AdapterSystem`] -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// use bevy_ecs::system::{Adapt, AdapterSystem, RunSystemError}; -/// -/// // A system adapter that inverts the result of a system. -/// // NOTE: Instead of manually implementing this, you can just use `bevy_ecs::schedule::common_conditions::not`. -/// pub type NotSystem = AdapterSystem; -/// -/// // This struct is used to customize the behavior of our adapter. -/// pub struct NotMarker; -/// -/// impl Adapt for NotMarker -/// where -/// S: System, -/// S::Out: std::ops::Not, -/// { -/// type In = S::In; -/// type Out = ::Output; -/// -/// fn adapt( -/// &mut self, -/// input: ::Inner<'_>, -/// run_system: impl FnOnce(SystemIn<'_, S>) -> Result, -/// ) -> Result { -/// let result = run_system(input)?; -/// Ok(!result) -/// } -/// } -/// # let mut world = World::new(); -/// # let mut system = NotSystem::new(NotMarker, IntoSystem::into_system(|| false), "".into()); -/// # system.initialize(&mut world); -/// # assert!(system.run((), &mut world).unwrap()); -/// ``` -#[diagnostic::on_unimplemented( - message = "`{Self}` can not adapt a system of type `{S}`", - label = "invalid system adapter" -)] -pub trait Adapt: Send + Sync + 'static { - /// The [input](System::In) type for an [`AdapterSystem`]. - type In: SystemInput; - /// The [output](System::Out) type for an [`AdapterSystem`]. - type Out; - - /// When used in an [`AdapterSystem`], this function customizes how the system - /// is run and how its inputs/outputs are adapted. - fn adapt( - &mut self, - input: ::Inner<'_>, - run_system: impl FnOnce(SystemIn<'_, S>) -> Result, - ) -> Result; -} - -/// An [`IntoSystem`] creating an instance of [`AdapterSystem`]. -#[derive(Clone)] -pub struct IntoAdapterSystem { - func: Func, - system: S, -} - -impl IntoAdapterSystem { - /// Creates a new [`IntoSystem`] that uses `func` to adapt `system`, via the [`Adapt`] trait. - pub const fn new(func: Func, system: S) -> Self { - Self { func, system } - } -} - -#[doc(hidden)] -pub struct IsAdapterSystemMarker; - -impl IntoSystem - for IntoAdapterSystem -where - Func: Adapt, - I: SystemInput, - S: IntoSystem, -{ - type System = AdapterSystem; - - // Required method - fn into_system(this: Self) -> Self::System { - let system = IntoSystem::into_system(this.system); - let name = system.name(); - AdapterSystem::new(this.func, system, name) - } -} - -/// A [`System`] that takes the output of `S` and transforms it by applying `Func` to it. -#[derive(Clone)] -pub struct AdapterSystem { - func: Func, - system: S, - name: DebugName, -} - -impl AdapterSystem -where - Func: Adapt, - S: System, -{ - /// Creates a new [`System`] that uses `func` to adapt `system`, via the [`Adapt`] trait. - pub const fn new(func: Func, system: S, name: DebugName) -> Self { - Self { func, system, name } - } -} - -impl System for AdapterSystem -where - Func: Adapt, - S: System, -{ - type In = Func::In; - type Out = Func::Out; - - fn name(&self) -> DebugName { - self.name.clone() - } - - #[inline] - fn flags(&self) -> super::SystemStateFlags { - self.system.flags() - } - - #[inline] - unsafe fn run_unsafe( - &mut self, - input: SystemIn<'_, Self>, - world: UnsafeWorldCell, - ) -> Result { - // SAFETY: `system.run_unsafe` has the same invariants as `self.run_unsafe`. - self.func.adapt(input, |input| unsafe { - self.system.run_unsafe(input, world) - }) - } - - #[cfg(feature = "hotpatching")] - #[inline] - fn refresh_hotpatch(&mut self) { - self.system.refresh_hotpatch(); - } - - #[inline] - fn apply_deferred(&mut self, world: &mut crate::prelude::World) { - self.system.apply_deferred(world); - } - - #[inline] - fn queue_deferred(&mut self, world: crate::world::DeferredWorld) { - self.system.queue_deferred(world); - } - - #[inline] - unsafe fn validate_param_unsafe( - &mut self, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // SAFETY: Delegate to other `System` implementations. - unsafe { self.system.validate_param_unsafe(world) } - } - - fn initialize( - &mut self, - world: &mut crate::prelude::World, - ) -> crate::query::FilteredAccessSet { - self.system.initialize(world) - } - - fn check_change_tick(&mut self, check: crate::component::CheckChangeTicks) { - self.system.check_change_tick(check); - } - - fn default_system_sets(&self) -> Vec { - self.system.default_system_sets() - } - - fn get_last_run(&self) -> crate::component::Tick { - self.system.get_last_run() - } - - fn set_last_run(&mut self, last_run: crate::component::Tick) { - self.system.set_last_run(last_run); - } -} - -// SAFETY: The inner system is read-only. -unsafe impl ReadOnlySystem for AdapterSystem -where - Func: Adapt, - S: ReadOnlySystem, -{ -} - -impl Adapt for F -where - F: Send + Sync + 'static + FnMut(S::Out) -> Out, - S: System, -{ - type In = S::In; - type Out = Out; - - fn adapt( - &mut self, - input: ::Inner<'_>, - run_system: impl FnOnce(SystemIn<'_, S>) -> Result, - ) -> Result { - run_system(input).map(self) - } -} diff --git a/src/system/builder.rs b/src/system/builder.rs deleted file mode 100644 index 4f4366b..0000000 --- a/src/system/builder.rs +++ /dev/null @@ -1,1057 +0,0 @@ -use alloc::{boxed::Box, vec::Vec}; -use bevy_platform::cell::SyncCell; -use variadics_please::all_tuples; - -use crate::{ - prelude::QueryBuilder, - query::{QueryData, QueryFilter, QueryState}, - resource::Resource, - system::{ - DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemParam, - SystemParamValidationError, When, - }, - world::{ - FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut, - FilteredResourcesMutBuilder, FromWorld, World, - }, -}; -use core::fmt::Debug; - -use super::{Res, ResMut, SystemState}; - -/// A builder that can create a [`SystemParam`]. -/// -/// ``` -/// # use bevy_ecs::{ -/// # prelude::*, -/// # system::{SystemParam, ParamBuilder}, -/// # }; -/// # #[derive(Resource)] -/// # struct R; -/// # -/// # #[derive(SystemParam)] -/// # struct MyParam; -/// # -/// fn some_system(param: MyParam) {} -/// -/// fn build_system(builder: impl SystemParamBuilder) { -/// let mut world = World::new(); -/// // To build a system, create a tuple of `SystemParamBuilder`s -/// // with a builder for each parameter. -/// // Note that the builder for a system must be a tuple, -/// // even if there is only one parameter. -/// (builder,) -/// .build_state(&mut world) -/// .build_system(some_system); -/// } -/// -/// fn build_closure_system_infer(builder: impl SystemParamBuilder) { -/// let mut world = World::new(); -/// // Closures can be used in addition to named functions. -/// // If a closure is used, the parameter types must all be inferred -/// // from the builders, so you cannot use plain `ParamBuilder`. -/// (builder, ParamBuilder::resource()) -/// .build_state(&mut world) -/// .build_system(|param, res| { -/// let param: MyParam = param; -/// let res: Res = res; -/// }); -/// } -/// -/// fn build_closure_system_explicit(builder: impl SystemParamBuilder) { -/// let mut world = World::new(); -/// // Alternately, you can provide all types in the closure -/// // parameter list and call `build_any_system()`. -/// (builder, ParamBuilder) -/// .build_state(&mut world) -/// .build_any_system(|param: MyParam, res: Res| {}); -/// } -/// ``` -/// -/// See the documentation for individual builders for more examples. -/// -/// # List of Builders -/// -/// [`ParamBuilder`] can be used for parameters that don't require any special building. -/// Using a `ParamBuilder` will build the system parameter the same way it would be initialized in an ordinary system. -/// -/// `ParamBuilder` also provides factory methods that return a `ParamBuilder` typed as `impl SystemParamBuilder

` -/// for common system parameters that can be used to guide closure parameter inference. -/// -/// [`QueryParamBuilder`] can build a [`Query`] to add additional filters, -/// or to configure the components available to [`FilteredEntityRef`](crate::world::FilteredEntityRef) or [`FilteredEntityMut`](crate::world::FilteredEntityMut). -/// You can also use a [`QueryState`] to build a [`Query`]. -/// -/// [`LocalBuilder`] can build a [`Local`] to supply the initial value for the `Local`. -/// -/// [`FilteredResourcesParamBuilder`] can build a [`FilteredResources`], -/// and [`FilteredResourcesMutParamBuilder`] can build a [`FilteredResourcesMut`], -/// to configure the resources that can be accessed. -/// -/// [`DynParamBuilder`] can build a [`DynSystemParam`] to determine the type of the inner parameter, -/// and to supply any `SystemParamBuilder` it needs. -/// -/// Tuples of builders can build tuples of parameters, one builder for each element. -/// Note that since systems require a tuple as a parameter, the outer builder for a system will always be a tuple. -/// -/// A [`Vec`] of builders can build a `Vec` of parameters, one builder for each element. -/// -/// A [`ParamSetBuilder`] can build a [`ParamSet`]. -/// This can wrap either a tuple or a `Vec`, one builder for each element. -/// -/// A custom system param created with `#[derive(SystemParam)]` can be buildable if it includes a `#[system_param(builder)]` attribute. -/// See [the documentation for `SystemParam` derives](SystemParam#builders). -/// -/// # Safety -/// -/// The implementor must ensure that the state returned -/// from [`SystemParamBuilder::build`] is valid for `P`. -/// Note that the exact safety requiremensts depend on the implementation of [`SystemParam`], -/// so if `Self` is not a local type then you must call [`SystemParam::init_state`] -/// or another [`SystemParamBuilder::build`]. -pub unsafe trait SystemParamBuilder: Sized { - /// Registers any [`World`] access used by this [`SystemParam`] - /// and creates a new instance of this param's [`State`](SystemParam::State). - fn build(self, world: &mut World) -> P::State; - - /// Create a [`SystemState`] from a [`SystemParamBuilder`]. - /// To create a system, call [`SystemState::build_system`] on the result. - fn build_state(self, world: &mut World) -> SystemState

{ - SystemState::from_builder(world, self) - } -} - -/// A [`SystemParamBuilder`] for any [`SystemParam`] that uses its default initialization. -/// -/// ## Example -/// -/// ``` -/// # use bevy_ecs::{ -/// # prelude::*, -/// # system::{SystemParam, ParamBuilder}, -/// # }; -/// # -/// # #[derive(Component)] -/// # struct A; -/// # -/// # #[derive(Resource)] -/// # struct R; -/// # -/// # #[derive(SystemParam)] -/// # struct MyParam; -/// # -/// # let mut world = World::new(); -/// # world.insert_resource(R); -/// # -/// fn my_system(res: Res, param: MyParam, query: Query<&A>) { -/// // ... -/// } -/// -/// let system = ( -/// // A plain ParamBuilder can build any parameter type. -/// ParamBuilder, -/// // The `of::

()` method returns a `ParamBuilder` -/// // typed as `impl SystemParamBuilder

`. -/// ParamBuilder::of::(), -/// // The other factory methods return typed builders -/// // for common parameter types. -/// ParamBuilder::query::<&A>(), -/// ) -/// .build_state(&mut world) -/// .build_system(my_system); -/// ``` -#[derive(Default, Debug, Clone)] -pub struct ParamBuilder; - -// SAFETY: Calls `SystemParam::init_state` -unsafe impl SystemParamBuilder

for ParamBuilder { - fn build(self, world: &mut World) -> P::State { - P::init_state(world) - } -} - -impl ParamBuilder { - /// Creates a [`SystemParamBuilder`] for any [`SystemParam`] that uses its default initialization. - pub fn of() -> impl SystemParamBuilder { - Self - } - - /// Helper method for reading a [`Resource`] as a param, equivalent to `of::>()` - pub fn resource<'w, T: Resource>() -> impl SystemParamBuilder> { - Self - } - - /// Helper method for mutably accessing a [`Resource`] as a param, equivalent to `of::>()` - pub fn resource_mut<'w, T: Resource>() -> impl SystemParamBuilder> { - Self - } - - /// Helper method for adding a [`Local`] as a param, equivalent to `of::>()` - pub fn local<'s, T: FromWorld + Send + 'static>() -> impl SystemParamBuilder> { - Self - } - - /// Helper method for adding a [`Query`] as a param, equivalent to `of::>()` - pub fn query<'w, 's, D: QueryData + 'static>() -> impl SystemParamBuilder> - { - Self - } - - /// Helper method for adding a filtered [`Query`] as a param, equivalent to `of::>()` - pub fn query_filtered<'w, 's, D: QueryData + 'static, F: QueryFilter + 'static>( - ) -> impl SystemParamBuilder> { - Self - } -} - -// SAFETY: Any `QueryState` for the correct world is valid for `Query::State`, -// and we check the world during `build`. -unsafe impl<'w, 's, D: QueryData + 'static, F: QueryFilter + 'static> - SystemParamBuilder> for QueryState -{ - fn build(self, world: &mut World) -> QueryState { - self.validate_world(world.id()); - self - } -} - -/// A [`SystemParamBuilder`] for a [`Query`]. -/// This takes a closure accepting an `&mut` [`QueryBuilder`] and uses the builder to construct the query's state. -/// This can be used to add additional filters, -/// or to configure the components available to [`FilteredEntityRef`](crate::world::FilteredEntityRef) or [`FilteredEntityMut`](crate::world::FilteredEntityMut). -/// -/// ## Example -/// -/// ``` -/// # use bevy_ecs::{ -/// # prelude::*, -/// # system::{SystemParam, QueryParamBuilder}, -/// # }; -/// # -/// # #[derive(Component)] -/// # struct Player; -/// # -/// # let mut world = World::new(); -/// let system = (QueryParamBuilder::new(|builder| { -/// builder.with::(); -/// }),) -/// .build_state(&mut world) -/// .build_system(|query: Query<()>| { -/// for _ in &query { -/// // This only includes entities with a `Player` component. -/// } -/// }); -/// -/// // When collecting multiple builders into a `Vec`, -/// // use `new_box()` to erase the closure type. -/// let system = (vec![ -/// QueryParamBuilder::new_box(|builder| { -/// builder.with::(); -/// }), -/// QueryParamBuilder::new_box(|builder| { -/// builder.without::(); -/// }), -/// ],) -/// .build_state(&mut world) -/// .build_system(|query: Vec>| {}); -/// ``` -#[derive(Clone)] -pub struct QueryParamBuilder(T); - -impl QueryParamBuilder { - /// Creates a [`SystemParamBuilder`] for a [`Query`] that accepts a callback to configure the [`QueryBuilder`]. - pub fn new(f: T) -> Self - where - T: FnOnce(&mut QueryBuilder), - { - Self(f) - } -} - -impl<'a, D: QueryData, F: QueryFilter> - QueryParamBuilder) + 'a>> -{ - /// Creates a [`SystemParamBuilder`] for a [`Query`] that accepts a callback to configure the [`QueryBuilder`]. - /// This boxes the callback so that it has a common type and can be put in a `Vec`. - pub fn new_box(f: impl FnOnce(&mut QueryBuilder) + 'a) -> Self { - Self(Box::new(f)) - } -} - -// SAFETY: Any `QueryState` for the correct world is valid for `Query::State`, -// and `QueryBuilder` produces one with the given `world`. -unsafe impl< - 'w, - 's, - D: QueryData + 'static, - F: QueryFilter + 'static, - T: FnOnce(&mut QueryBuilder), - > SystemParamBuilder> for QueryParamBuilder -{ - fn build(self, world: &mut World) -> QueryState { - let mut builder = QueryBuilder::new(world); - (self.0)(&mut builder); - builder.build() - } -} - -macro_rules! impl_system_param_builder_tuple { - ($(#[$meta:meta])* $(($param: ident, $builder: ident)),*) => { - #[expect( - clippy::allow_attributes, - reason = "This is in a macro; as such, the below lints may not always apply." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - #[allow( - non_snake_case, - reason = "The variable names are provided by the macro caller, not by us." - )] - $(#[$meta])* - // SAFETY: implementors of each `SystemParamBuilder` in the tuple have validated their impls - unsafe impl<$($param: SystemParam,)* $($builder: SystemParamBuilder<$param>,)*> SystemParamBuilder<($($param,)*)> for ($($builder,)*) { - fn build(self, world: &mut World) -> <($($param,)*) as SystemParam>::State { - let ($($builder,)*) = self; - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples won't generate any calls to the system parameter builders." - )] - ($($builder.build(world),)*) - } - } - }; -} - -all_tuples!( - #[doc(fake_variadic)] - impl_system_param_builder_tuple, - 0, - 16, - P, - B -); - -// SAFETY: implementors of each `SystemParamBuilder` in the vec have validated their impls -unsafe impl> SystemParamBuilder> for Vec { - fn build(self, world: &mut World) -> as SystemParam>::State { - self.into_iter() - .map(|builder| builder.build(world)) - .collect() - } -} - -/// A [`SystemParamBuilder`] for a [`ParamSet`]. -/// -/// To build a [`ParamSet`] with a tuple of system parameters, pass a tuple of matching [`SystemParamBuilder`]s. -/// To build a [`ParamSet`] with a [`Vec`] of system parameters, pass a `Vec` of matching [`SystemParamBuilder`]s. -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::{prelude::*, system::*}; -/// # -/// # #[derive(Component)] -/// # struct Health; -/// # -/// # #[derive(Component)] -/// # struct Enemy; -/// # -/// # #[derive(Component)] -/// # struct Ally; -/// # -/// # let mut world = World::new(); -/// # -/// let system = (ParamSetBuilder(( -/// QueryParamBuilder::new(|builder| { -/// builder.with::(); -/// }), -/// QueryParamBuilder::new(|builder| { -/// builder.with::(); -/// }), -/// ParamBuilder, -/// )),) -/// .build_state(&mut world) -/// .build_system(buildable_system_with_tuple); -/// # world.run_system_once(system); -/// -/// fn buildable_system_with_tuple( -/// mut set: ParamSet<(Query<&mut Health>, Query<&mut Health>, &World)>, -/// ) { -/// // The first parameter is built from the first builder, -/// // so this will iterate over enemies. -/// for mut health in set.p0().iter_mut() {} -/// // And the second parameter is built from the second builder, -/// // so this will iterate over allies. -/// for mut health in set.p1().iter_mut() {} -/// // Parameters that don't need special building can use `ParamBuilder`. -/// let entities = set.p2().entities(); -/// } -/// -/// let system = (ParamSetBuilder(vec![ -/// QueryParamBuilder::new_box(|builder| { -/// builder.with::(); -/// }), -/// QueryParamBuilder::new_box(|builder| { -/// builder.with::(); -/// }), -/// ]),) -/// .build_state(&mut world) -/// .build_system(buildable_system_with_vec); -/// # world.run_system_once(system); -/// -/// fn buildable_system_with_vec(mut set: ParamSet>>) { -/// // As with tuples, the first parameter is built from the first builder, -/// // so this will iterate over enemies. -/// for mut health in set.get_mut(0).iter_mut() {} -/// // And the second parameter is built from the second builder, -/// // so this will iterate over allies. -/// for mut health in set.get_mut(1).iter_mut() {} -/// // You can iterate over the parameters either by index, -/// // or using the `for_each` method. -/// set.for_each(|mut query| for mut health in query.iter_mut() {}); -/// } -/// ``` -#[derive(Debug, Default, Clone)] -pub struct ParamSetBuilder(pub T); - -macro_rules! impl_param_set_builder_tuple { - ($(($param: ident, $builder: ident)),*) => { - #[expect( - clippy::allow_attributes, - reason = "This is in a macro; as such, the below lints may not always apply." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - #[allow( - non_snake_case, - reason = "The variable names are provided by the macro caller, not by us." - )] - // SAFETY: implementors of each `SystemParamBuilder` in the tuple have validated their impls - unsafe impl<'w, 's, $($param: SystemParam,)* $($builder: SystemParamBuilder<$param>,)*> SystemParamBuilder> for ParamSetBuilder<($($builder,)*)> { - fn build(self, world: &mut World) -> <($($param,)*) as SystemParam>::State { - let ParamSetBuilder(($($builder,)*)) = self; - ($($builder.build(world),)*) - } - } - }; -} - -all_tuples!(impl_param_set_builder_tuple, 1, 8, P, B); - -// SAFETY: implementors of each `SystemParamBuilder` in the vec have validated their impls -unsafe impl<'w, 's, P: SystemParam, B: SystemParamBuilder

> - SystemParamBuilder>> for ParamSetBuilder> -{ - fn build(self, world: &mut World) -> as SystemParam>::State { - self.0 - .into_iter() - .map(|builder| builder.build(world)) - .collect() - } -} - -/// A [`SystemParamBuilder`] for a [`DynSystemParam`]. -/// See the [`DynSystemParam`] docs for examples. -pub struct DynParamBuilder<'a>(Box DynSystemParamState + 'a>); - -impl<'a> DynParamBuilder<'a> { - /// Creates a new [`DynParamBuilder`] by wrapping a [`SystemParamBuilder`] of any type. - /// The built [`DynSystemParam`] can be downcast to `T`. - pub fn new(builder: impl SystemParamBuilder + 'a) -> Self { - Self(Box::new(|world| { - DynSystemParamState::new::(builder.build(world)) - })) - } -} - -// SAFETY: `DynSystemParam::get_param` will call `get_param` on the boxed `DynSystemParamState`, -// and the boxed builder was a valid implementation of `SystemParamBuilder` for that type. -// The resulting `DynSystemParam` can only perform access by downcasting to that param type. -unsafe impl<'a, 'w, 's> SystemParamBuilder> for DynParamBuilder<'a> { - fn build(self, world: &mut World) -> as SystemParam>::State { - (self.0)(world) - } -} - -/// A [`SystemParamBuilder`] for a [`Local`]. -/// The provided value will be used as the initial value of the `Local`. -/// -/// ## Example -/// -/// ``` -/// # use bevy_ecs::{ -/// # prelude::*, -/// # system::{SystemParam, LocalBuilder, RunSystemOnce}, -/// # }; -/// # -/// # let mut world = World::new(); -/// let system = (LocalBuilder(100),) -/// .build_state(&mut world) -/// .build_system(|local: Local| { -/// assert_eq!(*local, 100); -/// }); -/// # world.run_system_once(system); -/// ``` -#[derive(Default, Debug, Clone)] -pub struct LocalBuilder(pub T); - -// SAFETY: Any value of `T` is a valid state for `Local`. -unsafe impl<'s, T: FromWorld + Send + 'static> SystemParamBuilder> - for LocalBuilder -{ - fn build(self, _world: &mut World) -> as SystemParam>::State { - SyncCell::new(self.0) - } -} - -/// A [`SystemParamBuilder`] for a [`FilteredResources`]. -/// See the [`FilteredResources`] docs for examples. -#[derive(Clone)] -pub struct FilteredResourcesParamBuilder(T); - -impl FilteredResourcesParamBuilder { - /// Creates a [`SystemParamBuilder`] for a [`FilteredResources`] that accepts a callback to configure the [`FilteredResourcesBuilder`]. - pub fn new(f: T) -> Self - where - T: FnOnce(&mut FilteredResourcesBuilder), - { - Self(f) - } -} - -impl<'a> FilteredResourcesParamBuilder> { - /// Creates a [`SystemParamBuilder`] for a [`FilteredResources`] that accepts a callback to configure the [`FilteredResourcesBuilder`]. - /// This boxes the callback so that it has a common type. - pub fn new_box(f: impl FnOnce(&mut FilteredResourcesBuilder) + 'a) -> Self { - Self(Box::new(f)) - } -} - -// SAFETY: Any `Access` is a valid state for `FilteredResources`. -unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesBuilder)> - SystemParamBuilder> for FilteredResourcesParamBuilder -{ - fn build(self, world: &mut World) -> as SystemParam>::State { - let mut builder = FilteredResourcesBuilder::new(world); - (self.0)(&mut builder); - builder.build() - } -} - -/// A [`SystemParamBuilder`] for a [`FilteredResourcesMut`]. -/// See the [`FilteredResourcesMut`] docs for examples. -#[derive(Clone)] -pub struct FilteredResourcesMutParamBuilder(T); - -impl FilteredResourcesMutParamBuilder { - /// Creates a [`SystemParamBuilder`] for a [`FilteredResourcesMut`] that accepts a callback to configure the [`FilteredResourcesMutBuilder`]. - pub fn new(f: T) -> Self - where - T: FnOnce(&mut FilteredResourcesMutBuilder), - { - Self(f) - } -} - -impl<'a> FilteredResourcesMutParamBuilder> { - /// Creates a [`SystemParamBuilder`] for a [`FilteredResourcesMut`] that accepts a callback to configure the [`FilteredResourcesMutBuilder`]. - /// This boxes the callback so that it has a common type. - pub fn new_box(f: impl FnOnce(&mut FilteredResourcesMutBuilder) + 'a) -> Self { - Self(Box::new(f)) - } -} - -// SAFETY: Any `Access` is a valid state for `FilteredResourcesMut`. -unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)> - SystemParamBuilder> for FilteredResourcesMutParamBuilder -{ - fn build(self, world: &mut World) -> as SystemParam>::State { - let mut builder = FilteredResourcesMutBuilder::new(world); - (self.0)(&mut builder); - builder.build() - } -} - -/// A [`SystemParamBuilder`] for an [`Option`]. -#[derive(Clone)] -pub struct OptionBuilder(T); - -// SAFETY: `OptionBuilder` builds a state that is valid for `P`, and any state valid for `P` is valid for `Option

` -unsafe impl> SystemParamBuilder> - for OptionBuilder -{ - fn build(self, world: &mut World) -> as SystemParam>::State { - self.0.build(world) - } -} - -/// A [`SystemParamBuilder`] for a [`Result`] of [`SystemParamValidationError`]. -#[derive(Clone)] -pub struct ResultBuilder(T); - -// SAFETY: `ResultBuilder` builds a state that is valid for `P`, and any state valid for `P` is valid for `Result` -unsafe impl> - SystemParamBuilder> for ResultBuilder -{ - fn build( - self, - world: &mut World, - ) -> as SystemParam>::State { - self.0.build(world) - } -} - -/// A [`SystemParamBuilder`] for a [`When`]. -#[derive(Clone)] -pub struct WhenBuilder(T); - -// SAFETY: `WhenBuilder` builds a state that is valid for `P`, and any state valid for `P` is valid for `When

::Item<'s>; - -impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> ExclusiveSystemParam - for &'a mut QueryState -{ - type State = QueryState; - type Item<'s> = &'s mut QueryState; - - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { - QueryState::new(world) - } - - fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { - state - } -} - -impl<'a, P: SystemParam + 'static> ExclusiveSystemParam for &'a mut SystemState

{ - type State = SystemState

; - type Item<'s> = &'s mut SystemState

; - - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { - SystemState::new(world) - } - - fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { - state - } -} - -impl<'_s, T: FromWorld + Send + 'static> ExclusiveSystemParam for Local<'_s, T> { - type State = SyncCell; - type Item<'s> = Local<'s, T>; - - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { - SyncCell::new(T::from_world(world)) - } - - fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { - Local(state.get()) - } -} - -impl ExclusiveSystemParam for PhantomData { - type State = (); - type Item<'s> = PhantomData; - - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {} - - fn get_param<'s>(_state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { - PhantomData - } -} - -macro_rules! impl_exclusive_system_param_tuple { - ($(#[$meta:meta])* $($param: ident),*) => { - #[expect( - clippy::allow_attributes, - reason = "This is within a macro, and as such, the below lints may not always apply." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - $(#[$meta])* - impl<$($param: ExclusiveSystemParam),*> ExclusiveSystemParam for ($($param,)*) { - type State = ($($param::State,)*); - type Item<'s> = ($($param::Item<'s>,)*); - - #[inline] - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - (($($param::init(world, system_meta),)*)) - } - - #[inline] - fn get_param<'s>( - state: &'s mut Self::State, - system_meta: &SystemMeta, - ) -> Self::Item<'s> { - let ($($param,)*) = state; - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples won't have any params to get." - )] - ($($param::get_param($param, system_meta),)*) - } - } - }; -} - -all_tuples!( - #[doc(fake_variadic)] - impl_exclusive_system_param_tuple, - 0, - 16, - P -); - -#[cfg(test)] -mod tests { - use crate::{schedule::Schedule, system::Local, world::World}; - use alloc::vec::Vec; - use bevy_ecs_macros::Resource; - use core::marker::PhantomData; - - #[test] - fn test_exclusive_system_params() { - #[derive(Resource, Default)] - struct Res { - test_value: u32, - } - - fn my_system(world: &mut World, mut local: Local, _phantom: PhantomData>) { - assert_eq!(world.resource::().test_value, *local); - *local += 1; - world.resource_mut::().test_value += 1; - } - - let mut schedule = Schedule::default(); - schedule.add_systems(my_system); - - let mut world = World::default(); - world.init_resource::(); - - schedule.run(&mut world); - schedule.run(&mut world); - - assert_eq!(2, world.get_resource::().unwrap().test_value); - } -} diff --git a/src/system/function_system.rs b/src/system/function_system.rs deleted file mode 100644 index 35d7e70..0000000 --- a/src/system/function_system.rs +++ /dev/null @@ -1,995 +0,0 @@ -use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, - error::{BevyError, Result}, - never::Never, - prelude::FromWorld, - query::FilteredAccessSet, - schedule::{InternedSystemSet, SystemSet}, - system::{ - check_system_change_tick, ReadOnlySystemParam, System, SystemIn, SystemInput, SystemParam, - SystemParamItem, - }, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World, WorldId}, -}; - -use alloc::{borrow::Cow, vec, vec::Vec}; -use bevy_utils::prelude::DebugName; -use core::marker::PhantomData; -use variadics_please::all_tuples; - -#[cfg(feature = "trace")] -use tracing::{info_span, Span}; - -use super::{ - IntoSystem, ReadOnlySystem, RunSystemError, SystemParamBuilder, SystemParamValidationError, - SystemStateFlags, -}; - -/// The metadata of a [`System`]. -#[derive(Clone)] -pub struct SystemMeta { - pub(crate) name: DebugName, - // NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent - // SystemParams from overriding each other - flags: SystemStateFlags, - pub(crate) last_run: Tick, - #[cfg(feature = "trace")] - pub(crate) system_span: Span, - #[cfg(feature = "trace")] - pub(crate) commands_span: Span, -} - -impl SystemMeta { - pub(crate) fn new() -> Self { - let name = DebugName::type_name::(); - Self { - #[cfg(feature = "trace")] - system_span: info_span!("system", name = name.clone().as_string()), - #[cfg(feature = "trace")] - commands_span: info_span!("system_commands", name = name.clone().as_string()), - name, - flags: SystemStateFlags::empty(), - last_run: Tick::new(0), - } - } - - /// Returns the system's name - #[inline] - pub fn name(&self) -> &DebugName { - &self.name - } - - /// Sets the name of this system. - /// - /// Useful to give closure systems more readable and unique names for debugging and tracing. - #[inline] - pub fn set_name(&mut self, new_name: impl Into>) { - let new_name: Cow<'static, str> = new_name.into(); - #[cfg(feature = "trace")] - { - let name = new_name.as_ref(); - self.system_span = info_span!("system", name = name); - self.commands_span = info_span!("system_commands", name = name); - } - self.name = new_name.into(); - } - - /// Returns true if the system is [`Send`]. - #[inline] - pub fn is_send(&self) -> bool { - !self.flags.intersects(SystemStateFlags::NON_SEND) - } - - /// Sets the system to be not [`Send`]. - /// - /// This is irreversible. - #[inline] - pub fn set_non_send(&mut self) { - self.flags |= SystemStateFlags::NON_SEND; - } - - /// Returns true if the system has deferred [`SystemParam`]'s - #[inline] - pub fn has_deferred(&self) -> bool { - self.flags.intersects(SystemStateFlags::DEFERRED) - } - - /// Marks the system as having deferred buffers like [`Commands`](`super::Commands`) - /// This lets the scheduler insert [`ApplyDeferred`](`crate::prelude::ApplyDeferred`) systems automatically. - #[inline] - pub fn set_has_deferred(&mut self) { - self.flags |= SystemStateFlags::DEFERRED; - } -} - -// TODO: Actually use this in FunctionSystem. We should probably only do this once Systems are constructed using a World reference -// (to avoid the need for unwrapping to retrieve SystemMeta) -/// Holds on to persistent state required to drive [`SystemParam`] for a [`System`]. -/// -/// This is a powerful and convenient tool for working with exclusive world access, -/// allowing you to fetch data from the [`World`] as if you were running a [`System`]. -/// However, simply calling `world::run_system(my_system)` using a [`World::run_system`](World::run_system) -/// can be significantly simpler and ensures that change detection and command flushing work as expected. -/// -/// Borrow-checking is handled for you, allowing you to mutably access multiple compatible system parameters at once, -/// and arbitrary system parameters (like [`EventWriter`](crate::event::EventWriter)) can be conveniently fetched. -/// -/// For an alternative approach to split mutable access to the world, see [`World::resource_scope`]. -/// -/// # Warning -/// -/// [`SystemState`] values created can be cached to improve performance, -/// and *must* be cached and reused in order for system parameters that rely on local state to work correctly. -/// These include: -/// - [`Added`](crate::query::Added), [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) query filters -/// - [`Local`](crate::system::Local) variables that hold state -/// - [`EventReader`](crate::event::EventReader) system parameters, which rely on a [`Local`](crate::system::Local) to track which events have been seen -/// -/// Note that this is automatically handled for you when using a [`World::run_system`](World::run_system). -/// -/// # Example -/// -/// Basic usage: -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::SystemState; -/// # use bevy_ecs::event::Events; -/// # -/// # #[derive(Event, BufferedEvent)] -/// # struct MyEvent; -/// # #[derive(Resource)] -/// # struct MyResource(u32); -/// # -/// # #[derive(Component)] -/// # struct MyComponent; -/// # -/// // Work directly on the `World` -/// let mut world = World::new(); -/// world.init_resource::>(); -/// -/// // Construct a `SystemState` struct, passing in a tuple of `SystemParam` -/// // as if you were writing an ordinary system. -/// let mut system_state: SystemState<( -/// EventWriter, -/// Option>, -/// Query<&MyComponent>, -/// )> = SystemState::new(&mut world); -/// -/// // Use system_state.get_mut(&mut world) and unpack your system parameters into variables! -/// // system_state.get(&world) provides read-only versions of your system parameters instead. -/// let (event_writer, maybe_resource, query) = system_state.get_mut(&mut world); -/// -/// // If you are using `Commands`, you can choose when you want to apply them to the world. -/// // You need to manually call `.apply(world)` on the `SystemState` to apply them. -/// ``` -/// Caching: -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::SystemState; -/// # use bevy_ecs::event::Events; -/// # -/// # #[derive(Event, BufferedEvent)] -/// # struct MyEvent; -/// #[derive(Resource)] -/// struct CachedSystemState { -/// event_state: SystemState>, -/// } -/// -/// // Create and store a system state once -/// let mut world = World::new(); -/// world.init_resource::>(); -/// let initial_state: SystemState> = SystemState::new(&mut world); -/// -/// // The system state is cached in a resource -/// world.insert_resource(CachedSystemState { -/// event_state: initial_state, -/// }); -/// -/// // Later, fetch the cached system state, saving on overhead -/// world.resource_scope(|world, mut cached_state: Mut| { -/// let mut event_reader = cached_state.event_state.get_mut(world); -/// -/// for events in event_reader.read() { -/// println!("Hello World!"); -/// } -/// }); -/// ``` -pub struct SystemState { - meta: SystemMeta, - param_state: Param::State, - world_id: WorldId, -} - -// Allow closure arguments to be inferred. -// For a closure to be used as a `SystemParamFunction`, it needs to be generic in any `'w` or `'s` lifetimes. -// Rust will only infer a closure to be generic over lifetimes if it's passed to a function with a Fn constraint. -// So, generate a function for each arity with an explicit `FnMut` constraint to enable higher-order lifetimes, -// along with a regular `SystemParamFunction` constraint to allow the system to be built. -macro_rules! impl_build_system { - ($(#[$meta:meta])* $($param: ident),*) => { - $(#[$meta])* - impl<$($param: SystemParam),*> SystemState<($($param,)*)> { - /// Create a [`FunctionSystem`] from a [`SystemState`]. - /// This method signature allows type inference of closure parameters for a system with no input. - /// You can use [`SystemState::build_system_with_input()`] if you have input, or [`SystemState::build_any_system()`] if you don't need type inference. - pub fn build_system< - InnerOut: IntoResult, - Out: 'static, - Marker, - F: FnMut($(SystemParamItem<$param>),*) -> InnerOut - + SystemParamFunction - > - ( - self, - func: F, - ) -> FunctionSystem - { - self.build_any_system(func) - } - - /// Create a [`FunctionSystem`] from a [`SystemState`]. - /// This method signature allows type inference of closure parameters for a system with input. - /// You can use [`SystemState::build_system()`] if you have no input, or [`SystemState::build_any_system()`] if you don't need type inference. - pub fn build_system_with_input< - Input: SystemInput, - InnerOut: IntoResult, - Out: 'static, - Marker, - F: FnMut(Input, $(SystemParamItem<$param>),*) -> InnerOut - + SystemParamFunction, - >( - self, - func: F, - ) -> FunctionSystem { - self.build_any_system(func) - } - } - } -} - -all_tuples!( - #[doc(fake_variadic)] - impl_build_system, - 0, - 16, - P -); - -impl SystemState { - /// Creates a new [`SystemState`] with default state. - pub fn new(world: &mut World) -> Self { - let mut meta = SystemMeta::new::(); - meta.last_run = world.change_tick().relative_to(Tick::MAX); - let param_state = Param::init_state(world); - let mut component_access_set = FilteredAccessSet::new(); - // We need to call `init_access` to ensure there are no panics from conflicts within `Param`, - // even though we don't use the calculated access. - Param::init_access(¶m_state, &mut meta, &mut component_access_set, world); - Self { - meta, - param_state, - world_id: world.id(), - } - } - - /// Create a [`SystemState`] from a [`SystemParamBuilder`] - pub(crate) fn from_builder(world: &mut World, builder: impl SystemParamBuilder) -> Self { - let mut meta = SystemMeta::new::(); - meta.last_run = world.change_tick().relative_to(Tick::MAX); - let param_state = builder.build(world); - let mut component_access_set = FilteredAccessSet::new(); - // We need to call `init_access` to ensure there are no panics from conflicts within `Param`, - // even though we don't use the calculated access. - Param::init_access(¶m_state, &mut meta, &mut component_access_set, world); - Self { - meta, - param_state, - world_id: world.id(), - } - } - - /// Create a [`FunctionSystem`] from a [`SystemState`]. - /// This method signature allows any system function, but the compiler will not perform type inference on closure parameters. - /// You can use [`SystemState::build_system()`] or [`SystemState::build_system_with_input()`] to get type inference on parameters. - pub fn build_any_system(self, func: F) -> FunctionSystem - where - F: SystemParamFunction>, - { - FunctionSystem { - func, - #[cfg(feature = "hotpatching")] - current_ptr: subsecond::HotFn::current(>::run) - .ptr_address(), - state: Some(FunctionSystemState { - param: self.param_state, - world_id: self.world_id, - }), - system_meta: self.meta, - marker: PhantomData, - } - } - - /// Gets the metadata for this instance. - #[inline] - pub fn meta(&self) -> &SystemMeta { - &self.meta - } - - /// Gets the metadata for this instance. - #[inline] - pub fn meta_mut(&mut self) -> &mut SystemMeta { - &mut self.meta - } - - /// Retrieve the [`SystemParam`] values. This can only be called when all parameters are read-only. - #[inline] - pub fn get<'w, 's>(&'s mut self, world: &'w World) -> SystemParamItem<'w, 's, Param> - where - Param: ReadOnlySystemParam, - { - self.validate_world(world.id()); - // SAFETY: Param is read-only and doesn't allow mutable access to World. - // It also matches the World this SystemState was created with. - unsafe { self.get_unchecked(world.as_unsafe_world_cell_readonly()) } - } - - /// Retrieve the mutable [`SystemParam`] values. - #[inline] - pub fn get_mut<'w, 's>(&'s mut self, world: &'w mut World) -> SystemParamItem<'w, 's, Param> { - self.validate_world(world.id()); - // SAFETY: World is uniquely borrowed and matches the World this SystemState was created with. - unsafe { self.get_unchecked(world.as_unsafe_world_cell()) } - } - - /// Applies all state queued up for [`SystemParam`] values. For example, this will apply commands queued up - /// by a [`Commands`](`super::Commands`) parameter to the given [`World`]. - /// This function should be called manually after the values returned by [`SystemState::get`] and [`SystemState::get_mut`] - /// are finished being used. - pub fn apply(&mut self, world: &mut World) { - Param::apply(&mut self.param_state, &self.meta, world); - } - - /// Wrapper over [`SystemParam::validate_param`]. - /// - /// # Safety - /// - /// - The passed [`UnsafeWorldCell`] must have read-only access to - /// world data in `component_access_set`. - /// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state). - pub unsafe fn validate_param( - state: &mut Self, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // SAFETY: Delegated to existing `SystemParam` implementations. - unsafe { Param::validate_param(&mut state.param_state, &state.meta, world) } - } - - /// Returns `true` if `world_id` matches the [`World`] that was used to call [`SystemState::new`]. - /// Otherwise, this returns false. - #[inline] - pub fn matches_world(&self, world_id: WorldId) -> bool { - self.world_id == world_id - } - - /// Asserts that the [`SystemState`] matches the provided world. - #[inline] - #[track_caller] - fn validate_world(&self, world_id: WorldId) { - #[inline(never)] - #[track_caller] - #[cold] - fn panic_mismatched(this: WorldId, other: WorldId) -> ! { - panic!("Encountered a mismatched World. This SystemState was created from {this:?}, but a method was called using {other:?}."); - } - - if !self.matches_world(world_id) { - panic_mismatched(self.world_id, world_id); - } - } - - /// Has no effect - #[inline] - #[deprecated( - since = "0.17.0", - note = "No longer has any effect. Calls may be removed." - )] - pub fn update_archetypes(&mut self, _world: &World) {} - - /// Has no effect - #[inline] - #[deprecated( - since = "0.17.0", - note = "No longer has any effect. Calls may be removed." - )] - pub fn update_archetypes_unsafe_world_cell(&mut self, _world: UnsafeWorldCell) {} - - /// Identical to [`SystemState::get`]. - #[inline] - #[deprecated(since = "0.17.0", note = "Call `SystemState::get` instead.")] - pub fn get_manual<'w, 's>(&'s mut self, world: &'w World) -> SystemParamItem<'w, 's, Param> - where - Param: ReadOnlySystemParam, - { - self.get(world) - } - - /// Identical to [`SystemState::get_mut`]. - #[inline] - #[deprecated(since = "0.17.0", note = "Call `SystemState::get_mut` instead.")] - pub fn get_manual_mut<'w, 's>( - &'s mut self, - world: &'w mut World, - ) -> SystemParamItem<'w, 's, Param> { - self.get_mut(world) - } - - /// Identical to [`SystemState::get_unchecked`]. - /// - /// # Safety - /// This call might access any of the input parameters in a way that violates Rust's mutability rules. Make sure the data - /// access is safe in the context of global [`World`] access. The passed-in [`World`] _must_ be the [`World`] the [`SystemState`] was - /// created with. - #[inline] - #[deprecated(since = "0.17.0", note = "Call `SystemState::get_unchecked` instead.")] - pub unsafe fn get_unchecked_manual<'w, 's>( - &'s mut self, - world: UnsafeWorldCell<'w>, - ) -> SystemParamItem<'w, 's, Param> { - // SAFETY: Caller ensures safety requirements - unsafe { self.get_unchecked(world) } - } - - /// Retrieve the [`SystemParam`] values. - /// - /// # Safety - /// This call might access any of the input parameters in a way that violates Rust's mutability rules. Make sure the data - /// access is safe in the context of global [`World`] access. The passed-in [`World`] _must_ be the [`World`] the [`SystemState`] was - /// created with. - #[inline] - pub unsafe fn get_unchecked<'w, 's>( - &'s mut self, - world: UnsafeWorldCell<'w>, - ) -> SystemParamItem<'w, 's, Param> { - let change_tick = world.increment_change_tick(); - // SAFETY: The invariants are upheld by the caller. - unsafe { self.fetch(world, change_tick) } - } - - /// # Safety - /// This call might access any of the input parameters in a way that violates Rust's mutability rules. Make sure the data - /// access is safe in the context of global [`World`] access. The passed-in [`World`] _must_ be the [`World`] the [`SystemState`] was - /// created with. - #[inline] - unsafe fn fetch<'w, 's>( - &'s mut self, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> SystemParamItem<'w, 's, Param> { - // SAFETY: The invariants are upheld by the caller. - let param = - unsafe { Param::get_param(&mut self.param_state, &self.meta, world, change_tick) }; - self.meta.last_run = change_tick; - param - } - - /// Returns a reference to the current system param states. - pub fn param_state(&self) -> &Param::State { - &self.param_state - } - - /// Returns a mutable reference to the current system param states. - /// Marked as unsafe because modifying the system states may result in violation to certain - /// assumptions made by the [`SystemParam`]. Use with care. - /// - /// # Safety - /// Modifying the system param states may have unintended consequences. - /// The param state is generally considered to be owned by the [`SystemParam`]. Modifications - /// should respect any invariants as required by the [`SystemParam`]. - /// For example, modifying the system state of [`ResMut`](crate::system::ResMut) will obviously create issues. - pub unsafe fn param_state_mut(&mut self) -> &mut Param::State { - &mut self.param_state - } -} - -impl FromWorld for SystemState { - fn from_world(world: &mut World) -> Self { - Self::new(world) - } -} - -/// The [`System`] counter part of an ordinary function. -/// -/// You get this by calling [`IntoSystem::into_system`] on a function that only accepts -/// [`SystemParam`]s. The output of the system becomes the functions return type, while the input -/// becomes the functions first parameter or `()` if no such parameter exists. -/// -/// [`FunctionSystem`] must be `.initialized` before they can be run. -/// -/// The [`Clone`] implementation for [`FunctionSystem`] returns a new instance which -/// is NOT initialized. The cloned system must also be `.initialized` before it can be run. -pub struct FunctionSystem -where - F: SystemParamFunction, -{ - func: F, - #[cfg(feature = "hotpatching")] - current_ptr: subsecond::HotFnPtr, - state: Option>, - system_meta: SystemMeta, - // NOTE: PhantomData T> gives this safe Send/Sync impls - marker: PhantomData (Marker, Out)>, -} - -/// The state of a [`FunctionSystem`], which must be initialized with -/// [`System::initialize`] before the system can be run. A panic will occur if -/// the system is run without being initialized. -struct FunctionSystemState { - /// The cached state of the system's [`SystemParam`]s. - param: P::State, - /// The id of the [`World`] this system was initialized with. If the world - /// passed to [`System::run_unsafe`] or [`System::validate_param_unsafe`] does not match - /// this id, a panic will occur. - world_id: WorldId, -} - -impl FunctionSystem -where - F: SystemParamFunction, -{ - /// Return this system with a new name. - /// - /// Useful to give closure systems more readable and unique names for debugging and tracing. - pub fn with_name(mut self, new_name: impl Into>) -> Self { - self.system_meta.set_name(new_name.into()); - self - } -} - -// De-initializes the cloned system. -impl Clone for FunctionSystem -where - F: SystemParamFunction + Clone, -{ - fn clone(&self) -> Self { - Self { - func: self.func.clone(), - #[cfg(feature = "hotpatching")] - current_ptr: subsecond::HotFn::current(>::run) - .ptr_address(), - state: None, - system_meta: SystemMeta::new::(), - marker: PhantomData, - } - } -} - -/// A marker type used to distinguish regular function systems from exclusive function systems. -#[doc(hidden)] -pub struct IsFunctionSystem; - -impl IntoSystem for F -where - Out: 'static, - Marker: 'static, - F: SystemParamFunction>, -{ - type System = FunctionSystem; - fn into_system(func: Self) -> Self::System { - FunctionSystem { - func, - #[cfg(feature = "hotpatching")] - current_ptr: subsecond::HotFn::current(>::run) - .ptr_address(), - state: None, - system_meta: SystemMeta::new::(), - marker: PhantomData, - } - } -} - -/// A type that may be converted to the output of a [`System`]. -/// This is used to allow systems to return either a plain value or a [`Result`]. -pub trait IntoResult: Sized { - /// Converts this type into the system output type. - fn into_result(self) -> Result; -} - -impl IntoResult for T { - fn into_result(self) -> Result { - Ok(self) - } -} - -impl IntoResult for Result { - fn into_result(self) -> Result { - self - } -} - -impl IntoResult for Result { - fn into_result(self) -> Result { - Ok(self?) - } -} - -// The `!` impl can't be generic in `Out`, since that would overlap with -// `impl IntoResult for T` when `T` = `!`. -// Use explicit impls for `()` and `bool` so diverging functions -// can be used for systems and conditions. -impl IntoResult<()> for Never { - fn into_result(self) -> Result<(), RunSystemError> { - self - } -} - -impl IntoResult for Never { - fn into_result(self) -> Result { - self - } -} - -impl FunctionSystem -where - F: SystemParamFunction, -{ - /// Message shown when a system isn't initialized - // When lines get too long, rustfmt can sometimes refuse to format them. - // Work around this by storing the message separately. - const ERROR_UNINITIALIZED: &'static str = - "System's state was not found. Did you forget to initialize this system before running it?"; -} - -impl System for FunctionSystem -where - Marker: 'static, - Out: 'static, - F: SystemParamFunction>, -{ - type In = F::In; - type Out = Out; - - #[inline] - fn name(&self) -> DebugName { - self.system_meta.name.clone() - } - - #[inline] - fn flags(&self) -> SystemStateFlags { - self.system_meta.flags - } - - #[inline] - unsafe fn run_unsafe( - &mut self, - input: SystemIn<'_, Self>, - world: UnsafeWorldCell, - ) -> Result { - #[cfg(feature = "trace")] - let _span_guard = self.system_meta.system_span.enter(); - - let change_tick = world.increment_change_tick(); - - let state = self.state.as_mut().expect(Self::ERROR_UNINITIALIZED); - assert_eq!(state.world_id, world.id(), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with."); - // SAFETY: - // - The above assert ensures the world matches. - // - All world accesses used by `F::Param` have been registered, so the caller - // will ensure that there are no data access conflicts. - let params = - unsafe { F::Param::get_param(&mut state.param, &self.system_meta, world, change_tick) }; - - #[cfg(feature = "hotpatching")] - let out = { - let mut hot_fn = subsecond::HotFn::current(>::run); - // SAFETY: - // - pointer used to call is from the current jump table - unsafe { - hot_fn - .try_call_with_ptr(self.current_ptr, (&mut self.func, input, params)) - .expect("Error calling hotpatched system. Run a full rebuild") - } - }; - #[cfg(not(feature = "hotpatching"))] - let out = self.func.run(input, params); - - self.system_meta.last_run = change_tick; - IntoResult::into_result(out) - } - - #[cfg(feature = "hotpatching")] - #[inline] - fn refresh_hotpatch(&mut self) { - let new = subsecond::HotFn::current(>::run).ptr_address(); - if new != self.current_ptr { - log::debug!("system {} hotpatched", self.name()); - } - self.current_ptr = new; - } - - #[inline] - fn apply_deferred(&mut self, world: &mut World) { - let param_state = &mut self.state.as_mut().expect(Self::ERROR_UNINITIALIZED).param; - F::Param::apply(param_state, &self.system_meta, world); - } - - #[inline] - fn queue_deferred(&mut self, world: DeferredWorld) { - let param_state = &mut self.state.as_mut().expect(Self::ERROR_UNINITIALIZED).param; - F::Param::queue(param_state, &self.system_meta, world); - } - - #[inline] - unsafe fn validate_param_unsafe( - &mut self, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - let state = self.state.as_mut().expect(Self::ERROR_UNINITIALIZED); - assert_eq!(state.world_id, world.id(), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with."); - // SAFETY: - // - The above assert ensures the world matches. - // - All world accesses used by `F::Param` have been registered, so the caller - // will ensure that there are no data access conflicts. - unsafe { F::Param::validate_param(&mut state.param, &self.system_meta, world) } - } - - #[inline] - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { - if let Some(state) = &self.state { - assert_eq!( - state.world_id, - world.id(), - "System built with a different world than the one it was added to.", - ); - } - let state = self.state.get_or_insert_with(|| FunctionSystemState { - param: F::Param::init_state(world), - world_id: world.id(), - }); - self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX); - let mut component_access_set = FilteredAccessSet::new(); - F::Param::init_access( - &state.param, - &mut self.system_meta, - &mut component_access_set, - world, - ); - component_access_set - } - - #[inline] - fn check_change_tick(&mut self, check: CheckChangeTicks) { - check_system_change_tick( - &mut self.system_meta.last_run, - check, - self.system_meta.name.clone(), - ); - } - - fn default_system_sets(&self) -> Vec { - let set = crate::schedule::SystemTypeSet::::new(); - vec![set.intern()] - } - - fn get_last_run(&self) -> Tick { - self.system_meta.last_run - } - - fn set_last_run(&mut self, last_run: Tick) { - self.system_meta.last_run = last_run; - } -} - -/// SAFETY: `F`'s param is [`ReadOnlySystemParam`], so this system will only read from the world. -unsafe impl ReadOnlySystem for FunctionSystem -where - Marker: 'static, - Out: 'static, - F: SystemParamFunction>, - F::Param: ReadOnlySystemParam, -{ -} - -/// A trait implemented for all functions that can be used as [`System`]s. -/// -/// This trait can be useful for making your own systems which accept other systems, -/// sometimes called higher order systems. -/// -/// This should be used in combination with [`ParamSet`] when calling other systems -/// within your system. -/// Using [`ParamSet`] in this case avoids [`SystemParam`] collisions. -/// -/// # Example -/// -/// To create something like [`PipeSystem`], but in entirely safe code. -/// -/// ``` -/// use std::num::ParseIntError; -/// -/// use bevy_ecs::prelude::*; -/// use bevy_ecs::system::StaticSystemInput; -/// -/// /// Pipe creates a new system which calls `a`, then calls `b` with the output of `a` -/// pub fn pipe( -/// mut a: A, -/// mut b: B, -/// ) -> impl FnMut(StaticSystemInput, ParamSet<(A::Param, B::Param)>) -> B::Out -/// where -/// // We need A and B to be systems, add those bounds -/// A: SystemParamFunction, -/// B: SystemParamFunction, -/// for<'a> B::In: SystemInput = A::Out>, -/// { -/// // The type of `params` is inferred based on the return of this function above -/// move |StaticSystemInput(a_in), mut params| { -/// let shared = a.run(a_in, params.p0()); -/// b.run(shared, params.p1()) -/// } -/// } -/// -/// // Usage example for `pipe`: -/// fn main() { -/// let mut world = World::default(); -/// world.insert_resource(Message("42".to_string())); -/// -/// // pipe the `parse_message_system`'s output into the `filter_system`s input -/// let mut piped_system = IntoSystem::into_system(pipe(parse_message, filter)); -/// piped_system.initialize(&mut world); -/// assert_eq!(piped_system.run((), &mut world).unwrap(), Some(42)); -/// } -/// -/// #[derive(Resource)] -/// struct Message(String); -/// -/// fn parse_message(message: Res) -> Result { -/// message.0.parse::() -/// } -/// -/// fn filter(In(result): In>) -> Option { -/// result.ok().filter(|&n| n < 100) -/// } -/// ``` -/// [`PipeSystem`]: crate::system::PipeSystem -/// [`ParamSet`]: crate::system::ParamSet -#[diagnostic::on_unimplemented( - message = "`{Self}` is not a valid system", - label = "invalid system" -)] -pub trait SystemParamFunction: Send + Sync + 'static { - /// The input type of this system. See [`System::In`]. - type In: SystemInput; - /// The return type of this system. See [`System::Out`]. - type Out; - - /// The [`SystemParam`]/s used by this system to access the [`World`]. - type Param: SystemParam; - - /// Executes this system once. See [`System::run`] or [`System::run_unsafe`]. - fn run( - &mut self, - input: ::Inner<'_>, - param_value: SystemParamItem, - ) -> Self::Out; -} - -/// A marker type used to distinguish function systems with and without input. -#[doc(hidden)] -pub struct HasSystemInput; - -macro_rules! impl_system_function { - ($($param: ident),*) => { - #[expect( - clippy::allow_attributes, - reason = "This is within a macro, and as such, the below lints may not always apply." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - impl SystemParamFunction Out> for Func - where - Func: Send + Sync + 'static, - for <'a> &'a mut Func: - FnMut($($param),*) -> Out + - FnMut($(SystemParamItem<$param>),*) -> Out, - Out: 'static - { - type In = (); - type Out = Out; - type Param = ($($param,)*); - #[inline] - fn run(&mut self, _input: (), param_value: SystemParamItem< ($($param,)*)>) -> Out { - // Yes, this is strange, but `rustc` fails to compile this impl - // without using this function. It fails to recognize that `func` - // is a function, potentially because of the multiple impls of `FnMut` - fn call_inner( - mut f: impl FnMut($($param,)*)->Out, - $($param: $param,)* - )->Out{ - f($($param,)*) - } - let ($($param,)*) = param_value; - call_inner(self, $($param),*) - } - } - - #[expect( - clippy::allow_attributes, - reason = "This is within a macro, and as such, the below lints may not always apply." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - impl SystemParamFunction<(HasSystemInput, fn(In, $($param,)*) -> Out)> for Func - where - Func: Send + Sync + 'static, - for <'a> &'a mut Func: - FnMut(In, $($param),*) -> Out + - FnMut(In::Param<'_>, $(SystemParamItem<$param>),*) -> Out, - In: SystemInput + 'static, - Out: 'static - { - type In = In; - type Out = Out; - type Param = ($($param,)*); - #[inline] - fn run(&mut self, input: In::Inner<'_>, param_value: SystemParamItem< ($($param,)*)>) -> Out { - fn call_inner( - _: PhantomData, - mut f: impl FnMut(In::Param<'_>, $($param,)*)->Out, - input: In::Inner<'_>, - $($param: $param,)* - )->Out{ - f(In::wrap(input), $($param,)*) - } - let ($($param,)*) = param_value; - call_inner(PhantomData::, self, input, $($param),*) - } - } - }; -} - -// Note that we rely on the highest impl to be <= the highest order of the tuple impls -// of `SystemParam` created. -all_tuples!(impl_system_function, 0, 16, F); - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn into_system_type_id_consistency() { - fn test(function: T) - where - T: IntoSystem + Copy, - { - fn reference_system() {} - - use core::any::TypeId; - - let system = IntoSystem::into_system(function); - - assert_eq!( - system.type_id(), - function.system_type_id(), - "System::type_id should be consistent with IntoSystem::system_type_id" - ); - - assert_eq!( - system.type_id(), - TypeId::of::(), - "System::type_id should be consistent with TypeId::of::()" - ); - - assert_ne!( - system.type_id(), - IntoSystem::into_system(reference_system).type_id(), - "Different systems should have different TypeIds" - ); - } - - fn function_system() {} - - test(function_system); - } -} diff --git a/src/system/input.rs b/src/system/input.rs deleted file mode 100644 index c8d799b..0000000 --- a/src/system/input.rs +++ /dev/null @@ -1,326 +0,0 @@ -use core::ops::{Deref, DerefMut}; - -use variadics_please::all_tuples; - -use crate::{bundle::Bundle, prelude::On, system::System}; - -/// Trait for types that can be used as input to [`System`]s. -/// -/// Provided implementations are: -/// - `()`: No input -/// - [`In`]: For values -/// - [`InRef`]: For read-only references to values -/// - [`InMut`]: For mutable references to values -/// - [`On`]: For [`ObserverSystem`]s -/// - [`StaticSystemInput`]: For arbitrary [`SystemInput`]s in generic contexts -/// - Tuples of [`SystemInput`]s up to 8 elements -/// -/// For advanced usecases, you can implement this trait for your own types. -/// -/// # Examples -/// -/// ## Tuples of [`SystemInput`]s -/// -/// ``` -/// use bevy_ecs::prelude::*; -/// -/// fn add((InMut(a), In(b)): (InMut, In)) { -/// *a += b; -/// } -/// # let mut world = World::new(); -/// # let mut add = IntoSystem::into_system(add); -/// # add.initialize(&mut world); -/// # let mut a = 12; -/// # let b = 24; -/// # add.run((&mut a, b), &mut world); -/// # assert_eq!(a, 36); -/// ``` -/// -/// [`ObserverSystem`]: crate::system::ObserverSystem -pub trait SystemInput: Sized { - /// The wrapper input type that is defined as the first argument to [`FunctionSystem`]s. - /// - /// [`FunctionSystem`]: crate::system::FunctionSystem - type Param<'i>: SystemInput; - /// The inner input type that is passed to functions that run systems, - /// such as [`System::run`]. - /// - /// [`System::run`]: crate::system::System::run - type Inner<'i>; - - /// Converts a [`SystemInput::Inner`] into a [`SystemInput::Param`]. - fn wrap(this: Self::Inner<'_>) -> Self::Param<'_>; -} - -/// Shorthand way to get the [`System::In`] for a [`System`] as a [`SystemInput::Inner`]. -pub type SystemIn<'a, S> = <::In as SystemInput>::Inner<'a>; - -/// A [`SystemInput`] type which denotes that a [`System`] receives -/// an input value of type `T` from its caller. -/// -/// [`System`]s may take an optional input which they require to be passed to them when they -/// are being [`run`](System::run). For [`FunctionSystem`]s the input may be marked -/// with this `In` type, but only the first param of a function may be tagged as an input. This also -/// means a system can only have one or zero input parameters. -/// -/// See [`SystemInput`] to learn more about system inputs in general. -/// -/// # Examples -/// -/// Here is a simple example of a system that takes a [`usize`] and returns the square of it. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// fn square(In(input): In) -> usize { -/// input * input -/// } -/// -/// let mut world = World::new(); -/// let mut square_system = IntoSystem::into_system(square); -/// square_system.initialize(&mut world); -/// -/// assert_eq!(square_system.run(12, &mut world).unwrap(), 144); -/// ``` -/// -/// [`SystemParam`]: crate::system::SystemParam -/// [`FunctionSystem`]: crate::system::FunctionSystem -#[derive(Debug)] -pub struct In(pub T); - -impl SystemInput for In { - type Param<'i> = In; - type Inner<'i> = T; - - fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { - In(this) - } -} - -impl Deref for In { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for In { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// A [`SystemInput`] type which denotes that a [`System`] receives -/// a read-only reference to a value of type `T` from its caller. -/// -/// This is similar to [`In`] but takes a reference to a value instead of the value itself. -/// See [`InMut`] for the mutable version. -/// -/// See [`SystemInput`] to learn more about system inputs in general. -/// -/// # Examples -/// -/// Here is a simple example of a system that logs the passed in message. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use std::fmt::Write as _; -/// # -/// #[derive(Resource, Default)] -/// struct Log(String); -/// -/// fn log(InRef(msg): InRef, mut log: ResMut) { -/// writeln!(log.0, "{}", msg).unwrap(); -/// } -/// -/// let mut world = World::new(); -/// world.init_resource::(); -/// let mut log_system = IntoSystem::into_system(log); -/// log_system.initialize(&mut world); -/// -/// log_system.run("Hello, world!", &mut world); -/// # assert_eq!(world.get_resource::().unwrap().0, "Hello, world!\n"); -/// ``` -/// -/// [`SystemParam`]: crate::system::SystemParam -#[derive(Debug)] -pub struct InRef<'i, T: ?Sized>(pub &'i T); - -impl SystemInput for InRef<'_, T> { - type Param<'i> = InRef<'i, T>; - type Inner<'i> = &'i T; - - fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { - InRef(this) - } -} - -impl<'i, T: ?Sized> Deref for InRef<'i, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.0 - } -} - -/// A [`SystemInput`] type which denotes that a [`System`] receives -/// a mutable reference to a value of type `T` from its caller. -/// -/// This is similar to [`In`] but takes a mutable reference to a value instead of the value itself. -/// See [`InRef`] for the read-only version. -/// -/// See [`SystemInput`] to learn more about system inputs in general. -/// -/// # Examples -/// -/// Here is a simple example of a system that takes a `&mut usize` and squares it. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// fn square(InMut(input): InMut) { -/// *input *= *input; -/// } -/// -/// let mut world = World::new(); -/// let mut square_system = IntoSystem::into_system(square); -/// square_system.initialize(&mut world); -/// -/// let mut value = 12; -/// square_system.run(&mut value, &mut world); -/// assert_eq!(value, 144); -/// ``` -/// -/// [`SystemParam`]: crate::system::SystemParam -#[derive(Debug)] -pub struct InMut<'a, T: ?Sized>(pub &'a mut T); - -impl SystemInput for InMut<'_, T> { - type Param<'i> = InMut<'i, T>; - type Inner<'i> = &'i mut T; - - fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { - InMut(this) - } -} - -impl<'i, T: ?Sized> Deref for InMut<'i, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.0 - } -} - -impl<'i, T: ?Sized> DerefMut for InMut<'i, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0 - } -} - -/// Used for [`ObserverSystem`]s. -/// -/// [`ObserverSystem`]: crate::system::ObserverSystem -impl SystemInput for On<'_, E, B> { - type Param<'i> = On<'i, E, B>; - type Inner<'i> = On<'i, E, B>; - - fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { - this - } -} - -/// A helper for using [`SystemInput`]s in generic contexts. -/// -/// This type is a [`SystemInput`] adapter which always has -/// `Self::Param == Self` (ignoring lifetimes for brevity), -/// no matter the argument [`SystemInput`] (`I`). -/// -/// This makes it useful for having arbitrary [`SystemInput`]s in -/// function systems. -/// -/// See [`SystemInput`] to learn more about system inputs in general. -pub struct StaticSystemInput<'a, I: SystemInput>(pub I::Inner<'a>); - -impl<'a, I: SystemInput> SystemInput for StaticSystemInput<'a, I> { - type Param<'i> = StaticSystemInput<'i, I>; - type Inner<'i> = I::Inner<'i>; - - fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { - StaticSystemInput(this) - } -} - -macro_rules! impl_system_input_tuple { - ($(#[$meta:meta])* $($name:ident),*) => { - $(#[$meta])* - impl<$($name: SystemInput),*> SystemInput for ($($name,)*) { - type Param<'i> = ($($name::Param<'i>,)*); - type Inner<'i> = ($($name::Inner<'i>,)*); - - #[expect( - clippy::allow_attributes, - reason = "This is in a macro; as such, the below lints may not always apply." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples won't have anything to wrap." - )] - fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { - let ($($name,)*) = this; - ($($name::wrap($name),)*) - } - } - }; -} - -all_tuples!( - #[doc(fake_variadic)] - impl_system_input_tuple, - 0, - 8, - I -); - -#[cfg(test)] -mod tests { - use crate::{ - system::{In, InMut, InRef, IntoSystem, System}, - world::World, - }; - - #[test] - fn two_tuple() { - fn by_value((In(a), In(b)): (In, In)) -> usize { - a + b - } - fn by_ref((InRef(a), InRef(b)): (InRef, InRef)) -> usize { - *a + *b - } - fn by_mut((InMut(a), In(b)): (InMut, In)) { - *a += b; - } - - let mut world = World::new(); - let mut by_value = IntoSystem::into_system(by_value); - let mut by_ref = IntoSystem::into_system(by_ref); - let mut by_mut = IntoSystem::into_system(by_mut); - - by_value.initialize(&mut world); - by_ref.initialize(&mut world); - by_mut.initialize(&mut world); - - let mut a = 12; - let b = 24; - - assert_eq!(by_value.run((a, b), &mut world).unwrap(), 36); - assert_eq!(by_ref.run((&a, &b), &mut world).unwrap(), 36); - by_mut.run((&mut a, b), &mut world).unwrap(); - assert_eq!(a, 36); - } -} diff --git a/src/system/observer_system.rs b/src/system/observer_system.rs deleted file mode 100644 index 862ebf7..0000000 --- a/src/system/observer_system.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::{ - prelude::{Bundle, On}, - system::System, -}; - -use super::IntoSystem; - -/// Implemented for [`System`]s that have [`On`] as the first argument. -pub trait ObserverSystem: - System, Out = Out> + Send + 'static -{ -} - -impl ObserverSystem for T where - T: System, Out = Out> + Send + 'static -{ -} - -/// Implemented for systems that convert into [`ObserverSystem`]. -/// -/// # Usage notes -/// -/// This trait should only be used as a bound for trait implementations or as an -/// argument to a function. If an observer system needs to be returned from a -/// function or stored somewhere, use [`ObserverSystem`] instead of this trait. -#[diagnostic::on_unimplemented( - message = "`{Self}` cannot become an `ObserverSystem`", - label = "the trait `IntoObserverSystem` is not implemented", - note = "for function `ObserverSystem`s, ensure the first argument is `On` and any subsequent ones are `SystemParam`" -)] -pub trait IntoObserverSystem: Send + 'static { - /// The type of [`System`] that this instance converts into. - type System: ObserverSystem; - - /// Turns this value into its corresponding [`System`]. - fn into_system(this: Self) -> Self::System; -} - -impl IntoObserverSystem for S -where - S: IntoSystem, Out, M> + Send + 'static, - S::System: ObserverSystem, - E: 'static, - B: Bundle, -{ - type System = S::System; - - fn into_system(this: Self) -> Self::System { - IntoSystem::into_system(this) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - event::Event, - observer::On, - system::{In, IntoSystem}, - world::World, - }; - - #[derive(Event)] - struct TriggerEvent; - - #[test] - fn test_piped_observer_systems_no_input() { - fn a(_: On) {} - fn b() {} - - let mut world = World::new(); - world.add_observer(a.pipe(b)); - } - - #[test] - fn test_piped_observer_systems_with_inputs() { - fn a(_: On) -> u32 { - 3 - } - fn b(_: In) {} - - let mut world = World::new(); - world.add_observer(a.pipe(b)); - } -} diff --git a/src/system/query.rs b/src/system/query.rs deleted file mode 100644 index 6e44301..0000000 --- a/src/system/query.rs +++ /dev/null @@ -1,2744 +0,0 @@ -use bevy_utils::prelude::DebugName; - -use crate::{ - batching::BatchingStrategy, - component::Tick, - entity::{Entity, EntityDoesNotExistError, EntityEquivalent, EntitySet, UniqueEntityArray}, - query::{ - DebugCheckedUnwrap, NopWorldQuery, QueryCombinationIter, QueryData, QueryEntityError, - QueryFilter, QueryIter, QueryManyIter, QueryManyUniqueIter, QueryParIter, QueryParManyIter, - QueryParManyUniqueIter, QuerySingleError, QueryState, ROQueryItem, ReadOnlyQueryData, - }, - world::unsafe_world_cell::UnsafeWorldCell, -}; -use core::{ - marker::PhantomData, - mem::MaybeUninit, - ops::{Deref, DerefMut}, -}; - -/// A [system parameter] that provides selective access to the [`Component`] data stored in a [`World`]. -/// -/// Queries enable systems to access [entity identifiers] and [components] without requiring direct access to the [`World`]. -/// Its iterators and getter methods return *query items*, which are types containing data related to an entity. -/// -/// `Query` is a generic data structure that accepts two type parameters: -/// -/// - **`D` (query data)**: -/// The type of data fetched by the query, which will be returned as the query item. -/// Only entities that match the requested data will generate an item. -/// Must implement the [`QueryData`] trait. -/// - **`F` (query filter)**: -/// An optional set of conditions that determine whether query items should be kept or discarded. -/// This defaults to [`unit`], which means no additional filters will be applied. -/// Must implement the [`QueryFilter`] trait. -/// -/// [system parameter]: crate::system::SystemParam -/// [`Component`]: crate::component::Component -/// [`World`]: crate::world::World -/// [entity identifiers]: Entity -/// [components]: crate::component::Component -/// -/// # Similar parameters -/// -/// `Query` has few sibling [`SystemParam`]s, which perform additional validation: -/// -/// - [`Single`] - Exactly one matching query item. -/// - [`Option`] - Zero or one matching query item. -/// - [`Populated`] - At least one matching query item. -/// -/// These parameters will prevent systems from running if their requirements are not met. -/// -/// [`SystemParam`]: crate::system::system_param::SystemParam -/// [`Option`]: Single -/// -/// # System parameter declaration -/// -/// A query should always be declared as a system parameter. -/// This section shows the most common idioms involving the declaration of `Query`. -/// -/// ## Component access -/// -/// You can fetch an entity's component by specifying a reference to that component in the query's data parameter: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// // A component can be accessed by a shared reference... -/// fn immutable_query(query: Query<&ComponentA>) { -/// // ... -/// } -/// -/// // ...or by a mutable reference. -/// fn mutable_query(query: Query<&mut ComponentA>) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_is_system(immutable_query); -/// # bevy_ecs::system::assert_is_system(mutable_query); -/// ``` -/// -/// Note that components need to be behind a reference (`&` or `&mut`), or the query will not compile: -/// -/// ```compile_fail,E0277 -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// // This needs to be `&ComponentA` or `&mut ComponentA` in order to compile. -/// fn invalid_query(query: Query) { -/// // ... -/// } -/// ``` -/// -/// ## Query filtering -/// -/// Setting the query filter type parameter will ensure that each query item satisfies the given condition: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// # #[derive(Component)] -/// # struct ComponentB; -/// # -/// // `ComponentA` data will be accessed, but only for entities that also contain `ComponentB`. -/// fn filtered_query(query: Query<&ComponentA, With>) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_is_system(filtered_query); -/// ``` -/// -/// Note that the filter is `With`, not `With<&ComponentB>`. Unlike query data, `With` -/// does not require components to be behind a reference. -/// -/// ## `QueryData` or `QueryFilter` tuples -/// -/// Using [`tuple`]s, each `Query` type parameter can contain multiple elements. -/// -/// In the following example two components are accessed simultaneously, and the query items are -/// filtered on two conditions: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// # #[derive(Component)] -/// # struct ComponentB; -/// # -/// # #[derive(Component)] -/// # struct ComponentC; -/// # -/// # #[derive(Component)] -/// # struct ComponentD; -/// # -/// fn complex_query( -/// query: Query<(&mut ComponentA, &ComponentB), (With, Without)> -/// ) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_is_system(complex_query); -/// ``` -/// -/// Note that this currently only works on tuples with 15 or fewer items. You may nest tuples to -/// get around this limit: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// # #[derive(Component)] -/// # struct ComponentB; -/// # -/// # #[derive(Component)] -/// # struct ComponentC; -/// # -/// # #[derive(Component)] -/// # struct ComponentD; -/// # -/// fn nested_query( -/// query: Query<(&ComponentA, &ComponentB, (&mut ComponentC, &mut ComponentD))> -/// ) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_is_system(nested_query); -/// ``` -/// -/// ## Entity identifier access -/// -/// You can access [`Entity`], the entity identifier, by including it in the query data parameter: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// fn entity_id_query(query: Query<(Entity, &ComponentA)>) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_is_system(entity_id_query); -/// ``` -/// -/// Be aware that [`Entity`] is not a component, so it does not need to be behind a reference. -/// -/// ## Optional component access -/// -/// A component can be made optional by wrapping it into an [`Option`]. In the following example, a -/// query item will still be generated even if the queried entity does not contain `ComponentB`. -/// When this is the case, `Option<&ComponentB>`'s corresponding value will be `None`. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// # #[derive(Component)] -/// # struct ComponentB; -/// # -/// // Queried items must contain `ComponentA`. If they also contain `ComponentB`, its value will -/// // be fetched as well. -/// fn optional_component_query(query: Query<(&ComponentA, Option<&ComponentB>)>) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_is_system(optional_component_query); -/// ``` -/// -/// Optional components can hurt performance in some cases, so please read the [performance] -/// section to learn more about them. Additionally, if you need to declare several optional -/// components, you may be interested in using [`AnyOf`]. -/// -/// [performance]: #performance -/// [`AnyOf`]: crate::query::AnyOf -/// -/// ## Disjoint queries -/// -/// A system cannot contain two queries that break Rust's mutability rules, or else it will panic -/// when initialized. This can often be fixed with the [`Without`] filter, which makes the queries -/// disjoint. -/// -/// In the following example, the two queries can mutably access the same `&mut Health` component -/// if an entity has both the `Player` and `Enemy` components. Bevy will catch this and panic, -/// however, instead of breaking Rust's mutability rules: -/// -/// ```should_panic -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct Health; -/// # -/// # #[derive(Component)] -/// # struct Player; -/// # -/// # #[derive(Component)] -/// # struct Enemy; -/// # -/// fn randomize_health( -/// player_query: Query<&mut Health, With>, -/// enemy_query: Query<&mut Health, With>, -/// ) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_system_does_not_conflict(randomize_health); -/// ``` -/// -/// Adding a [`Without`] filter will disjoint the queries. In the following example, any entity -/// that has both the `Player` and `Enemy` components will be excluded from _both_ queries: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct Health; -/// # -/// # #[derive(Component)] -/// # struct Player; -/// # -/// # #[derive(Component)] -/// # struct Enemy; -/// # -/// fn randomize_health( -/// player_query: Query<&mut Health, (With, Without)>, -/// enemy_query: Query<&mut Health, (With, Without)>, -/// ) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_system_does_not_conflict(randomize_health); -/// ``` -/// -/// An alternative solution to this problem would be to wrap the conflicting queries in -/// [`ParamSet`]. -/// -/// [`Without`]: crate::query::Without -/// [`ParamSet`]: crate::system::ParamSet -/// -/// ## Whole Entity Access -/// -/// [`EntityRef`] can be used in a query to gain read-only access to all components of an entity. -/// This is useful when dynamically fetching components instead of baking them into the query type. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// fn all_components_query(query: Query<(EntityRef, &ComponentA)>) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_is_system(all_components_query); -/// ``` -/// -/// As [`EntityRef`] can read any component on an entity, a query using it will conflict with *any* -/// mutable component access. -/// -/// ```should_panic -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// // `EntityRef` provides read access to *all* components on an entity. When combined with -/// // `&mut ComponentA` in the same query, it creates a conflict because `EntityRef` could read -/// // `&ComponentA` while `&mut ComponentA` attempts to modify it - violating Rust's borrowing -/// // rules. -/// fn invalid_query(query: Query<(EntityRef, &mut ComponentA)>) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_system_does_not_conflict(invalid_query); -/// ``` -/// -/// It is strongly advised to couple [`EntityRef`] queries with the use of either [`With`] / -/// [`Without`] filters or [`ParamSet`]s. Not only does this improve the performance and -/// parallelization of the system, but it enables systems to gain mutable access to other -/// components: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// # #[derive(Component)] -/// # struct ComponentB; -/// # -/// // The first query only reads entities that have `ComponentA`, while the second query only -/// // modifies entities that *don't* have `ComponentA`. Because neither query will access the same -/// // entity, this system does not conflict. -/// fn disjoint_query( -/// query_a: Query>, -/// query_b: Query<&mut ComponentB, Without>, -/// ) { -/// // ... -/// } -/// # -/// # bevy_ecs::system::assert_system_does_not_conflict(disjoint_query); -/// ``` -/// -/// The fundamental rule: [`EntityRef`]'s ability to read all components means it can never -/// coexist with mutable access. [`With`] / [`Without`] filters can guarantee this by keeping the -/// queries on completely separate entities. -/// -/// [`EntityRef`]: crate::world::EntityRef -/// [`With`]: crate::query::With -/// -/// # Accessing query items -/// -/// The following table summarizes the behavior of safe methods that can be used to get query -/// items: -/// -/// |Query methods|Effect| -/// |-|-| -/// |[`iter`]\[[`_mut`][`iter_mut`]\]|Returns an iterator over all query items.| -/// |[`iter[_mut]().for_each()`][`for_each`],
[`par_iter`]\[[`_mut`][`par_iter_mut`]\]|Runs a specified function for each query item.| -/// |[`iter_many`]\[[`_unique`][`iter_many_unique`]\]\[[`_mut`][`iter_many_mut`]\]|Iterates over query items that match a list of entities.| -/// |[`iter_combinations`]\[[`_mut`][`iter_combinations_mut`]\]|Iterates over all combinations of query items.| -/// |[`single`](Self::single)\[[`_mut`][`single_mut`]\]|Returns a single query item if only one exists.| -/// |[`get`]\[[`_mut`][`get_mut`]\]|Returns the query item for a specified entity.| -/// |[`get_many`]\[[`_unique`][`get_many_unique`]\]\[[`_mut`][`get_many_mut`]\]|Returns all query items that match a list of entities.| -/// -/// There are two methods for each type of query operation: immutable and mutable (ending with `_mut`). -/// When using immutable methods, the query items returned are of type [`ROQueryItem`], a read-only version of the query item. -/// In this circumstance, every mutable reference in the query fetch type parameter is substituted by a shared reference. -/// -/// [`iter`]: Self::iter -/// [`iter_mut`]: Self::iter_mut -/// [`for_each`]: #iteratorfor_each -/// [`par_iter`]: Self::par_iter -/// [`par_iter_mut`]: Self::par_iter_mut -/// [`iter_many`]: Self::iter_many -/// [`iter_many_unique`]: Self::iter_many_unique -/// [`iter_many_mut`]: Self::iter_many_mut -/// [`iter_combinations`]: Self::iter_combinations -/// [`iter_combinations_mut`]: Self::iter_combinations_mut -/// [`single_mut`]: Self::single_mut -/// [`get`]: Self::get -/// [`get_mut`]: Self::get_mut -/// [`get_many`]: Self::get_many -/// [`get_many_unique`]: Self::get_many_unique -/// [`get_many_mut`]: Self::get_many_mut -/// -/// # Performance -/// -/// Creating a `Query` is a low-cost constant operation. Iterating it, on the other hand, fetches -/// data from the world and generates items, which can have a significant computational cost. -/// -/// Two systems cannot be executed in parallel if both access the same component type where at -/// least one of the accesses is mutable. Because of this, it is recommended for queries to only -/// fetch mutable access to components when necessary, since immutable access can be parallelized. -/// -/// Query filters ([`With`] / [`Without`]) can improve performance because they narrow the kinds of -/// entities that can be fetched. Systems that access fewer kinds of entities are more likely to be -/// parallelized by the scheduler. -/// -/// On the other hand, be careful using optional components (`Option<&ComponentA>`) and -/// [`EntityRef`] because they broaden the amount of entities kinds that can be accessed. This is -/// especially true of a query that _only_ fetches optional components or [`EntityRef`], as the -/// query would iterate over all entities in the world. -/// -/// There are two types of [component storage types]: [`Table`] and [`SparseSet`]. [`Table`] offers -/// fast iteration speeds, but slower insertion and removal speeds. [`SparseSet`] is the opposite: -/// it offers fast component insertion and removal speeds, but slower iteration speeds. -/// -/// The following table compares the computational complexity of the various methods and -/// operations, where: -/// -/// - **n** is the number of entities that match the query. -/// - **r** is the number of elements in a combination. -/// - **k** is the number of involved entities in the operation. -/// - **a** is the number of archetypes in the world. -/// - **C** is the [binomial coefficient], used to count combinations. nCr is -/// read as "*n* choose *r*" and is equivalent to the number of distinct unordered subsets of *r* -/// elements that can be taken from a set of *n* elements. -/// -/// |Query operation|Computational complexity| -/// |-|-| -/// |[`iter`]\[[`_mut`][`iter_mut`]\]|O(n)| -/// |[`iter[_mut]().for_each()`][`for_each`],
[`par_iter`]\[[`_mut`][`par_iter_mut`]\]|O(n)| -/// |[`iter_many`]\[[`_mut`][`iter_many_mut`]\]|O(k)| -/// |[`iter_combinations`]\[[`_mut`][`iter_combinations_mut`]\]|O(nCr)| -/// |[`single`](Self::single)\[[`_mut`][`single_mut`]\]|O(a)| -/// |[`get`]\[[`_mut`][`get_mut`]\]|O(1)| -/// |[`get_many`]|O(k)| -/// |[`get_many_mut`]|O(k2)| -/// |Archetype-based filtering ([`With`], [`Without`], [`Or`])|O(a)| -/// |Change detection filtering ([`Added`], [`Changed`], [`Spawned`])|O(a + n)| -/// -/// [component storage types]: crate::component::StorageType -/// [`Table`]: crate::storage::Table -/// [`SparseSet`]: crate::storage::SparseSet -/// [binomial coefficient]: https://en.wikipedia.org/wiki/Binomial_coefficient -/// [`Or`]: crate::query::Or -/// [`Added`]: crate::query::Added -/// [`Changed`]: crate::query::Changed -/// [`Spawned`]: crate::query::Spawned -/// -/// # `Iterator::for_each` -/// -/// The `for_each` methods appear to be generally faster than `for`-loops when run on worlds with -/// high archetype fragmentation, and may enable additional optimizations like [autovectorization]. It -/// is strongly advised to only use [`Iterator::for_each`] if it tangibly improves performance. -/// *Always* profile or benchmark before and after the change! -/// -/// ```rust -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// # -/// fn system(query: Query<&ComponentA>) { -/// // This may result in better performance... -/// query.iter().for_each(|component| { -/// // ... -/// }); -/// -/// // ...than this. Always benchmark to validate the difference! -/// for component in query.iter() { -/// // ... -/// } -/// } -/// # -/// # bevy_ecs::system::assert_is_system(system); -/// ``` -/// -/// [autovectorization]: https://en.wikipedia.org/wiki/Automatic_vectorization -pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = ()> { - // SAFETY: Must have access to the components registered in `state`. - world: UnsafeWorldCell<'world>, - state: &'state QueryState, - last_run: Tick, - this_run: Tick, -} - -impl Clone for Query<'_, '_, D, F> { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Query<'_, '_, D, F> {} - -impl core::fmt::Debug for Query<'_, '_, D, F> { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_struct("Query") - .field("matched_entities", &self.iter().count()) - .field("state", &self.state) - .field("last_run", &self.last_run) - .field("this_run", &self.this_run) - .field("world", &self.world) - .finish() - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { - /// Creates a new query. - /// - /// # Safety - /// - /// * This will create a query that could violate memory safety rules. Make sure that this is only - /// called in ways that ensure the queries have unique mutable access. - /// * `world` must be the world used to create `state`. - #[inline] - pub(crate) unsafe fn new( - world: UnsafeWorldCell<'w>, - state: &'s QueryState, - last_run: Tick, - this_run: Tick, - ) -> Self { - Self { - world, - state, - last_run, - this_run, - } - } - - /// Returns another `Query` from this that fetches the read-only version of the query items. - /// - /// For example, `Query<(&mut D1, &D2, &mut D3), With>` will become `Query<(&D1, &D2, &D3), With>`. - /// This can be useful when working around the borrow checker, - /// or reusing functionality between systems via functions that accept query types. - /// - /// # See also - /// - /// [`into_readonly`](Self::into_readonly) for a version that consumes the `Query` to return one with the full `'world` lifetime. - pub fn as_readonly(&self) -> Query<'_, 's, D::ReadOnly, F> { - // SAFETY: The reborrowed query is converted to read-only, so it cannot perform mutable access, - // and the original query is held with a shared borrow, so it cannot perform mutable access either. - unsafe { self.reborrow_unsafe() }.into_readonly() - } - - /// Returns another `Query` from this does not return any data, which can be faster. - fn as_nop(&self) -> Query<'_, 's, NopWorldQuery, F> { - let new_state = self.state.as_nop(); - // SAFETY: - // - The reborrowed query is converted to read-only, so it cannot perform mutable access, - // and the original query is held with a shared borrow, so it cannot perform mutable access either. - // Note that although `NopWorldQuery` itself performs *no* access and could soundly alias a mutable query, - // it has the original `QueryState::component_access` and could be `transmute`d to a read-only query. - // - The world matches because it was the same one used to construct self. - unsafe { Query::new(self.world, new_state, self.last_run, self.this_run) } - } - - /// Returns another `Query` from this that fetches the read-only version of the query items. - /// - /// For example, `Query<(&mut D1, &D2, &mut D3), With>` will become `Query<(&D1, &D2, &D3), With>`. - /// This can be useful when working around the borrow checker, - /// or reusing functionality between systems via functions that accept query types. - /// - /// # See also - /// - /// [`as_readonly`](Self::as_readonly) for a version that borrows the `Query` instead of consuming it. - pub fn into_readonly(self) -> Query<'w, 's, D::ReadOnly, F> { - let new_state = self.state.as_readonly(); - // SAFETY: - // - This is memory safe because it turns the query immutable. - // - The world matches because it was the same one used to construct self. - unsafe { Query::new(self.world, new_state, self.last_run, self.this_run) } - } - - /// Returns a new `Query` reborrowing the access from this one. The current query will be unusable - /// while the new one exists. - /// - /// # Example - /// - /// For example this allows to call other methods or other systems that require an owned `Query` without - /// completely giving up ownership of it. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct ComponentA; - /// - /// fn helper_system(query: Query<&ComponentA>) { /* ... */} - /// - /// fn system(mut query: Query<&ComponentA>) { - /// helper_system(query.reborrow()); - /// // Can still use query here: - /// for component in &query { - /// // ... - /// } - /// } - /// ``` - pub fn reborrow(&mut self) -> Query<'_, 's, D, F> { - // SAFETY: this query is exclusively borrowed while the new one exists, so - // no overlapping access can occur. - unsafe { self.reborrow_unsafe() } - } - - /// Returns a new `Query` reborrowing the access from this one. - /// The current query will still be usable while the new one exists, but must not be used in a way that violates aliasing. - /// - /// # Safety - /// - /// This function makes it possible to violate Rust's aliasing guarantees. - /// You must make sure this call does not result in a mutable or shared reference to a component with a mutable reference. - /// - /// # See also - /// - /// - [`reborrow`](Self::reborrow) for the safe versions. - pub unsafe fn reborrow_unsafe(&self) -> Query<'_, 's, D, F> { - // SAFETY: - // - This is memory safe because the caller ensures that there are no conflicting references. - // - The world matches because it was the same one used to construct self. - unsafe { self.copy_unsafe() } - } - - /// Returns a new `Query` copying the access from this one. - /// The current query will still be usable while the new one exists, but must not be used in a way that violates aliasing. - /// - /// # Safety - /// - /// This function makes it possible to violate Rust's aliasing guarantees. - /// You must make sure this call does not result in a mutable or shared reference to a component with a mutable reference. - /// - /// # See also - /// - /// - [`reborrow_unsafe`](Self::reborrow_unsafe) for a safer version that constrains the returned `'w` lifetime to the length of the borrow. - unsafe fn copy_unsafe(&self) -> Query<'w, 's, D, F> { - // SAFETY: - // - This is memory safe because the caller ensures that there are no conflicting references. - // - The world matches because it was the same one used to construct self. - unsafe { Query::new(self.world, self.state, self.last_run, self.this_run) } - } - - /// Returns an [`Iterator`] over the read-only query items. - /// - /// This iterator is always guaranteed to return results from each matching entity once and only once. - /// Iteration order is not guaranteed. - /// - /// # Example - /// - /// Here, the `report_names_system` iterates over the `Player` component of every entity that contains it: - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct Player { name: String } - /// # - /// fn report_names_system(query: Query<&Player>) { - /// for player in &query { - /// println!("Say hello to {}!", player.name); - /// } - /// } - /// # bevy_ecs::system::assert_is_system(report_names_system); - /// ``` - /// - /// # See also - /// - /// [`iter_mut`](Self::iter_mut) for mutable query items. - #[inline] - pub fn iter(&self) -> QueryIter<'_, 's, D::ReadOnly, F> { - self.as_readonly().into_iter() - } - - /// Returns an [`Iterator`] over the query items. - /// - /// This iterator is always guaranteed to return results from each matching entity once and only once. - /// Iteration order is not guaranteed. - /// - /// # Example - /// - /// Here, the `gravity_system` updates the `Velocity` component of every entity that contains it: - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct Velocity { x: f32, y: f32, z: f32 } - /// fn gravity_system(mut query: Query<&mut Velocity>) { - /// const DELTA: f32 = 1.0 / 60.0; - /// for mut velocity in &mut query { - /// velocity.y -= 9.8 * DELTA; - /// } - /// } - /// # bevy_ecs::system::assert_is_system(gravity_system); - /// ``` - /// - /// # See also - /// - /// [`iter`](Self::iter) for read-only query items. - #[inline] - pub fn iter_mut(&mut self) -> QueryIter<'_, 's, D, F> { - self.reborrow().into_iter() - } - - /// Returns a [`QueryCombinationIter`] over all combinations of `K` read-only query items without repetition. - /// - /// This iterator is always guaranteed to return results from each unique pair of matching entities. - /// Iteration order is not guaranteed. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Component)] - /// # struct ComponentA; - /// # - /// fn some_system(query: Query<&ComponentA>) { - /// for [a1, a2] in query.iter_combinations() { - /// // ... - /// } - /// } - /// ``` - /// - /// # See also - /// - /// - [`iter_combinations_mut`](Self::iter_combinations_mut) for mutable query item combinations. - /// - [`iter_combinations_inner`](Self::iter_combinations_inner) for mutable query item combinations with the full `'world` lifetime. - #[inline] - pub fn iter_combinations( - &self, - ) -> QueryCombinationIter<'_, 's, D::ReadOnly, F, K> { - self.as_readonly().iter_combinations_inner() - } - - /// Returns a [`QueryCombinationIter`] over all combinations of `K` query items without repetition. - /// - /// This iterator is always guaranteed to return results from each unique pair of matching entities. - /// Iteration order is not guaranteed. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Component)] - /// # struct ComponentA; - /// fn some_system(mut query: Query<&mut ComponentA>) { - /// let mut combinations = query.iter_combinations_mut(); - /// while let Some([mut a1, mut a2]) = combinations.fetch_next() { - /// // mutably access components data - /// } - /// } - /// ``` - /// - /// # See also - /// - /// - [`iter_combinations`](Self::iter_combinations) for read-only query item combinations. - /// - [`iter_combinations_inner`](Self::iter_combinations_inner) for mutable query item combinations with the full `'world` lifetime. - #[inline] - pub fn iter_combinations_mut( - &mut self, - ) -> QueryCombinationIter<'_, 's, D, F, K> { - self.reborrow().iter_combinations_inner() - } - - /// Returns a [`QueryCombinationIter`] over all combinations of `K` query items without repetition. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// This iterator is always guaranteed to return results from each unique pair of matching entities. - /// Iteration order is not guaranteed. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Component)] - /// # struct ComponentA; - /// fn some_system(query: Query<&mut ComponentA>) { - /// let mut combinations = query.iter_combinations_inner(); - /// while let Some([mut a1, mut a2]) = combinations.fetch_next() { - /// // mutably access components data - /// } - /// } - /// ``` - /// - /// # See also - /// - /// - [`iter_combinations`](Self::iter_combinations) for read-only query item combinations. - /// - [`iter_combinations_mut`](Self::iter_combinations_mut) for mutable query item combinations. - #[inline] - pub fn iter_combinations_inner(self) -> QueryCombinationIter<'w, 's, D, F, K> { - // SAFETY: `self.world` has permission to access the required components. - unsafe { QueryCombinationIter::new(self.world, self.state, self.last_run, self.this_run) } - } - - /// Returns an [`Iterator`] over the read-only query items generated from an [`Entity`] list. - /// - /// Items are returned in the order of the list of entities, and may not be unique if the input - /// doesn't guarantee uniqueness. Entities that don't match the query are skipped. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Component)] - /// # struct Counter { - /// # value: i32 - /// # } - /// # - /// // A component containing an entity list. - /// #[derive(Component)] - /// struct Friends { - /// list: Vec, - /// } - /// - /// fn system( - /// friends_query: Query<&Friends>, - /// counter_query: Query<&Counter>, - /// ) { - /// for friends in &friends_query { - /// for counter in counter_query.iter_many(&friends.list) { - /// println!("Friend's counter: {}", counter.value); - /// } - /// } - /// } - /// # bevy_ecs::system::assert_is_system(system); - /// ``` - /// - /// # See also - /// - /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. - /// - [`iter_many_inner`](Self::iter_many_inner) to get mutable query items with the full `'world` lifetime. - #[inline] - pub fn iter_many>( - &self, - entities: EntityList, - ) -> QueryManyIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> { - self.as_readonly().iter_many_inner(entities) - } - - /// Returns an iterator over the query items generated from an [`Entity`] list. - /// - /// Items are returned in the order of the list of entities, and may not be unique if the input - /// doesn't guarantee uniqueness. Entities that don't match the query are skipped. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// #[derive(Component)] - /// struct Counter { - /// value: i32 - /// } - /// - /// #[derive(Component)] - /// struct Friends { - /// list: Vec, - /// } - /// - /// fn system( - /// friends_query: Query<&Friends>, - /// mut counter_query: Query<&mut Counter>, - /// ) { - /// for friends in &friends_query { - /// let mut iter = counter_query.iter_many_mut(&friends.list); - /// while let Some(mut counter) = iter.fetch_next() { - /// println!("Friend's counter: {}", counter.value); - /// counter.value += 1; - /// } - /// } - /// } - /// # bevy_ecs::system::assert_is_system(system); - /// ``` - /// # See also - /// - /// - [`iter_many`](Self::iter_many) to get read-only query items. - /// - [`iter_many_inner`](Self::iter_many_inner) to get mutable query items with the full `'world` lifetime. - #[inline] - pub fn iter_many_mut>( - &mut self, - entities: EntityList, - ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { - self.reborrow().iter_many_inner(entities) - } - - /// Returns an iterator over the query items generated from an [`Entity`] list. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// Items are returned in the order of the list of entities, and may not be unique if the input - /// doesn't guarantee uniqueness. Entities that don't match the query are skipped. - /// - /// # See also - /// - /// - [`iter_many`](Self::iter_many) to get read-only query items. - /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. - #[inline] - pub fn iter_many_inner>( - self, - entities: EntityList, - ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> { - // SAFETY: `self.world` has permission to access the required components. - unsafe { - QueryManyIter::new( - self.world, - self.state, - entities, - self.last_run, - self.this_run, - ) - } - } - - /// Returns an [`Iterator`] over the unique read-only query items generated from an [`EntitySet`]. - /// - /// Items are returned in the order of the list of entities. Entities that don't match the query are skipped. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::{prelude::*, entity::{EntitySet, UniqueEntityIter}}; - /// # use core::slice; - /// # #[derive(Component)] - /// # struct Counter { - /// # value: i32 - /// # } - /// # - /// // `Friends` ensures that it only lists unique entities. - /// #[derive(Component)] - /// struct Friends { - /// unique_list: Vec, - /// } - /// - /// impl<'a> IntoIterator for &'a Friends { - /// - /// type Item = &'a Entity; - /// type IntoIter = UniqueEntityIter>; - /// - /// fn into_iter(self) -> Self::IntoIter { - /// // SAFETY: `Friends` ensures that it unique_list contains only unique entities. - /// unsafe { UniqueEntityIter::from_iterator_unchecked(self.unique_list.iter()) } - /// } - /// } - /// - /// fn system( - /// friends_query: Query<&Friends>, - /// counter_query: Query<&Counter>, - /// ) { - /// for friends in &friends_query { - /// for counter in counter_query.iter_many_unique(friends) { - /// println!("Friend's counter: {:?}", counter.value); - /// } - /// } - /// } - /// # bevy_ecs::system::assert_is_system(system); - /// ``` - /// - /// # See also - /// - /// - [`iter_many_unique_mut`](Self::iter_many_unique_mut) to get mutable query items. - /// - [`iter_many_unique_inner`](Self::iter_many_unique_inner) to get with the actual "inner" world lifetime. - #[inline] - pub fn iter_many_unique( - &self, - entities: EntityList, - ) -> QueryManyUniqueIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> { - self.as_readonly().iter_many_unique_inner(entities) - } - - /// Returns an iterator over the unique query items generated from an [`EntitySet`]. - /// - /// Items are returned in the order of the list of entities. Entities that don't match the query are skipped. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::{prelude::*, entity::{EntitySet, UniqueEntityIter}}; - /// # use core::slice; - /// #[derive(Component)] - /// struct Counter { - /// value: i32 - /// } - /// - /// // `Friends` ensures that it only lists unique entities. - /// #[derive(Component)] - /// struct Friends { - /// unique_list: Vec, - /// } - /// - /// impl<'a> IntoIterator for &'a Friends { - /// type Item = &'a Entity; - /// type IntoIter = UniqueEntityIter>; - /// - /// fn into_iter(self) -> Self::IntoIter { - /// // SAFETY: `Friends` ensures that it unique_list contains only unique entities. - /// unsafe { UniqueEntityIter::from_iterator_unchecked(self.unique_list.iter()) } - /// } - /// } - /// - /// fn system( - /// friends_query: Query<&Friends>, - /// mut counter_query: Query<&mut Counter>, - /// ) { - /// for friends in &friends_query { - /// for mut counter in counter_query.iter_many_unique_mut(friends) { - /// println!("Friend's counter: {:?}", counter.value); - /// counter.value += 1; - /// } - /// } - /// } - /// # bevy_ecs::system::assert_is_system(system); - /// ``` - /// # See also - /// - /// - [`iter_many_unique`](Self::iter_many_unique) to get read-only query items. - /// - [`iter_many_unique_inner`](Self::iter_many_unique_inner) to get with the actual "inner" world lifetime. - #[inline] - pub fn iter_many_unique_mut( - &mut self, - entities: EntityList, - ) -> QueryManyUniqueIter<'_, 's, D, F, EntityList::IntoIter> { - self.reborrow().iter_many_unique_inner(entities) - } - - /// Returns an iterator over the unique query items generated from an [`EntitySet`]. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// Items are returned in the order of the list of entities. Entities that don't match the query are skipped. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::{prelude::*, entity::{EntitySet, UniqueEntityIter}}; - /// # use core::slice; - /// #[derive(Component)] - /// struct Counter { - /// value: i32 - /// } - /// - /// // `Friends` ensures that it only lists unique entities. - /// #[derive(Component)] - /// struct Friends { - /// unique_list: Vec, - /// } - /// - /// impl<'a> IntoIterator for &'a Friends { - /// type Item = &'a Entity; - /// type IntoIter = UniqueEntityIter>; - /// - /// fn into_iter(self) -> Self::IntoIter { - /// // SAFETY: `Friends` ensures that it unique_list contains only unique entities. - /// unsafe { UniqueEntityIter::from_iterator_unchecked(self.unique_list.iter()) } - /// } - /// } - /// - /// fn system( - /// friends_query: Query<&Friends>, - /// mut counter_query: Query<&mut Counter>, - /// ) { - /// let friends = friends_query.single().unwrap(); - /// for mut counter in counter_query.iter_many_unique_inner(friends) { - /// println!("Friend's counter: {:?}", counter.value); - /// counter.value += 1; - /// } - /// } - /// # bevy_ecs::system::assert_is_system(system); - /// ``` - /// # See also - /// - /// - [`iter_many_unique`](Self::iter_many_unique) to get read-only query items. - /// - [`iter_many_unique_mut`](Self::iter_many_unique_mut) to get mutable query items. - #[inline] - pub fn iter_many_unique_inner( - self, - entities: EntityList, - ) -> QueryManyUniqueIter<'w, 's, D, F, EntityList::IntoIter> { - // SAFETY: `self.world` has permission to access the required components. - unsafe { - QueryManyUniqueIter::new( - self.world, - self.state, - entities, - self.last_run, - self.this_run, - ) - } - } - - /// Returns an [`Iterator`] over the query items. - /// - /// This iterator is always guaranteed to return results from each matching entity once and only once. - /// Iteration order is not guaranteed. - /// - /// # Safety - /// - /// This function makes it possible to violate Rust's aliasing guarantees. - /// You must make sure this call does not result in multiple mutable references to the same component. - /// - /// # See also - /// - /// - [`iter`](Self::iter) and [`iter_mut`](Self::iter_mut) for the safe versions. - #[inline] - pub unsafe fn iter_unsafe(&self) -> QueryIter<'_, 's, D, F> { - // SAFETY: The caller promises that this will not result in multiple mutable references. - unsafe { self.reborrow_unsafe() }.into_iter() - } - - /// Iterates over all possible combinations of `K` query items without repetition. - /// - /// This iterator is always guaranteed to return results from each unique pair of matching entities. - /// Iteration order is not guaranteed. - /// - /// # Safety - /// - /// This allows aliased mutability. - /// You must make sure this call does not result in multiple mutable references to the same component. - /// - /// # See also - /// - /// - [`iter_combinations`](Self::iter_combinations) and [`iter_combinations_mut`](Self::iter_combinations_mut) for the safe versions. - #[inline] - pub unsafe fn iter_combinations_unsafe( - &self, - ) -> QueryCombinationIter<'_, 's, D, F, K> { - // SAFETY: The caller promises that this will not result in multiple mutable references. - unsafe { self.reborrow_unsafe() }.iter_combinations_inner() - } - - /// Returns an [`Iterator`] over the query items generated from an [`Entity`] list. - /// - /// Items are returned in the order of the list of entities, and may not be unique if the input - /// doesnn't guarantee uniqueness. Entities that don't match the query are skipped. - /// - /// # Safety - /// - /// This allows aliased mutability and does not check for entity uniqueness. - /// You must make sure this call does not result in multiple mutable references to the same component. - /// Particular care must be taken when collecting the data (rather than iterating over it one item at a time) such as via [`Iterator::collect`]. - /// - /// # See also - /// - /// - [`iter_many_mut`](Self::iter_many_mut) to safely access the query items. - pub unsafe fn iter_many_unsafe>( - &self, - entities: EntityList, - ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { - // SAFETY: The caller promises that this will not result in multiple mutable references. - unsafe { self.reborrow_unsafe() }.iter_many_inner(entities) - } - - /// Returns an [`Iterator`] over the unique query items generated from an [`Entity`] list. - /// - /// Items are returned in the order of the list of entities. Entities that don't match the query are skipped. - /// - /// # Safety - /// - /// This allows aliased mutability. - /// You must make sure this call does not result in multiple mutable references to the same component. - /// - /// # See also - /// - /// - [`iter_many_unique`](Self::iter_many_unique) to get read-only query items. - /// - [`iter_many_unique_mut`](Self::iter_many_unique_mut) to get mutable query items. - /// - [`iter_many_unique_inner`](Self::iter_many_unique_inner) to get with the actual "inner" world lifetime. - pub unsafe fn iter_many_unique_unsafe( - &self, - entities: EntityList, - ) -> QueryManyUniqueIter<'_, 's, D, F, EntityList::IntoIter> { - // SAFETY: The caller promises that this will not result in multiple mutable references. - unsafe { self.reborrow_unsafe() }.iter_many_unique_inner(entities) - } - - /// Returns a parallel iterator over the query results for the given [`World`]. - /// - /// This parallel iterator is always guaranteed to return results from each matching entity once and - /// only once. Iteration order and thread assignment is not guaranteed. - /// - /// If the `multithreaded` feature is disabled, iterating with this operates identically to [`Iterator::for_each`] - /// on [`QueryIter`]. - /// - /// This can only be called for read-only queries, see [`par_iter_mut`] for write-queries. - /// - /// Note that you must use the `for_each` method to iterate over the - /// results, see [`par_iter_mut`] for an example. - /// - /// [`par_iter_mut`]: Self::par_iter_mut - /// [`World`]: crate::world::World - #[inline] - pub fn par_iter(&self) -> QueryParIter<'_, 's, D::ReadOnly, F> { - self.as_readonly().par_iter_inner() - } - - /// Returns a parallel iterator over the query results for the given [`World`]. - /// - /// This parallel iterator is always guaranteed to return results from each matching entity once and - /// only once. Iteration order and thread assignment is not guaranteed. - /// - /// If the `multithreaded` feature is disabled, iterating with this operates identically to [`Iterator::for_each`] - /// on [`QueryIter`]. - /// - /// This can only be called for mutable queries, see [`par_iter`] for read-only-queries. - /// - /// # Example - /// - /// Here, the `gravity_system` updates the `Velocity` component of every entity that contains it: - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct Velocity { x: f32, y: f32, z: f32 } - /// fn gravity_system(mut query: Query<&mut Velocity>) { - /// const DELTA: f32 = 1.0 / 60.0; - /// query.par_iter_mut().for_each(|mut velocity| { - /// velocity.y -= 9.8 * DELTA; - /// }); - /// } - /// # bevy_ecs::system::assert_is_system(gravity_system); - /// ``` - /// - /// [`par_iter`]: Self::par_iter - /// [`World`]: crate::world::World - #[inline] - pub fn par_iter_mut(&mut self) -> QueryParIter<'_, 's, D, F> { - self.reborrow().par_iter_inner() - } - - /// Returns a parallel iterator over the query results for the given [`World`](crate::world::World). - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// This parallel iterator is always guaranteed to return results from each matching entity once and - /// only once. Iteration order and thread assignment is not guaranteed. - /// - /// If the `multithreaded` feature is disabled, iterating with this operates identically to [`Iterator::for_each`] - /// on [`QueryIter`]. - /// - /// # Example - /// - /// Here, the `gravity_system` updates the `Velocity` component of every entity that contains it: - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct Velocity { x: f32, y: f32, z: f32 } - /// fn gravity_system(query: Query<&mut Velocity>) { - /// const DELTA: f32 = 1.0 / 60.0; - /// query.par_iter_inner().for_each(|mut velocity| { - /// velocity.y -= 9.8 * DELTA; - /// }); - /// } - /// # bevy_ecs::system::assert_is_system(gravity_system); - /// ``` - #[inline] - pub fn par_iter_inner(self) -> QueryParIter<'w, 's, D, F> { - QueryParIter { - world: self.world, - state: self.state, - last_run: self.last_run, - this_run: self.this_run, - batching_strategy: BatchingStrategy::new(), - } - } - - /// Returns a parallel iterator over the read-only query items generated from an [`Entity`] list. - /// - /// Entities that don't match the query are skipped. Iteration order and thread assignment is not guaranteed. - /// - /// If the `multithreaded` feature is disabled, iterating with this operates identically to [`Iterator::for_each`] - /// on [`QueryManyIter`]. - /// - /// This can only be called for read-only queries. To avoid potential aliasing, there is no `par_iter_many_mut` equivalent. - /// See [`par_iter_many_unique_mut`] for an alternative using [`EntitySet`]. - /// - /// Note that you must use the `for_each` method to iterate over the - /// results, see [`par_iter_mut`] for an example. - /// - /// [`par_iter_many_unique_mut`]: Self::par_iter_many_unique_mut - /// [`par_iter_mut`]: Self::par_iter_mut - #[inline] - pub fn par_iter_many>( - &self, - entities: EntityList, - ) -> QueryParManyIter<'_, 's, D::ReadOnly, F, EntityList::Item> { - QueryParManyIter { - world: self.world, - state: self.state.as_readonly(), - entity_list: entities.into_iter().collect(), - last_run: self.last_run, - this_run: self.this_run, - batching_strategy: BatchingStrategy::new(), - } - } - - /// Returns a parallel iterator over the unique read-only query items generated from an [`EntitySet`]. - /// - /// Entities that don't match the query are skipped. Iteration order and thread assignment is not guaranteed. - /// - /// If the `multithreaded` feature is disabled, iterating with this operates identically to [`Iterator::for_each`] - /// on [`QueryManyUniqueIter`]. - /// - /// This can only be called for read-only queries, see [`par_iter_many_unique_mut`] for write-queries. - /// - /// Note that you must use the `for_each` method to iterate over the - /// results, see [`par_iter_mut`] for an example. - /// - /// [`par_iter_many_unique_mut`]: Self::par_iter_many_unique_mut - /// [`par_iter_mut`]: Self::par_iter_mut - #[inline] - pub fn par_iter_many_unique>( - &self, - entities: EntityList, - ) -> QueryParManyUniqueIter<'_, 's, D::ReadOnly, F, EntityList::Item> { - QueryParManyUniqueIter { - world: self.world, - state: self.state.as_readonly(), - entity_list: entities.into_iter().collect(), - last_run: self.last_run, - this_run: self.this_run, - batching_strategy: BatchingStrategy::new(), - } - } - - /// Returns a parallel iterator over the unique query items generated from an [`EntitySet`]. - /// - /// Entities that don't match the query are skipped. Iteration order and thread assignment is not guaranteed. - /// - /// If the `multithreaded` feature is disabled, iterating with this operates identically to [`Iterator::for_each`] - /// on [`QueryManyUniqueIter`]. - /// - /// This can only be called for mutable queries, see [`par_iter_many_unique`] for read-only-queries. - /// - /// Note that you must use the `for_each` method to iterate over the - /// results, see [`par_iter_mut`] for an example. - /// - /// [`par_iter_many_unique`]: Self::par_iter_many_unique - /// [`par_iter_mut`]: Self::par_iter_mut - #[inline] - pub fn par_iter_many_unique_mut>( - &mut self, - entities: EntityList, - ) -> QueryParManyUniqueIter<'_, 's, D, F, EntityList::Item> { - QueryParManyUniqueIter { - world: self.world, - state: self.state, - entity_list: entities.into_iter().collect(), - last_run: self.last_run, - this_run: self.this_run, - batching_strategy: BatchingStrategy::new(), - } - } - - /// Returns the read-only query item for the given [`Entity`]. - /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. - /// - /// This is always guaranteed to run in `O(1)` time. - /// - /// # Example - /// - /// Here, `get` is used to retrieve the exact query item of the entity specified by the `SelectedCharacter` resource. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Resource)] - /// # struct SelectedCharacter { entity: Entity } - /// # #[derive(Component)] - /// # struct Character { name: String } - /// # - /// fn print_selected_character_name_system( - /// query: Query<&Character>, - /// selection: Res - /// ) - /// { - /// if let Ok(selected_character) = query.get(selection.entity) { - /// println!("{}", selected_character.name); - /// } - /// } - /// # bevy_ecs::system::assert_is_system(print_selected_character_name_system); - /// ``` - /// - /// # See also - /// - /// - [`get_mut`](Self::get_mut) to get a mutable query item. - #[inline] - pub fn get(&self, entity: Entity) -> Result, QueryEntityError> { - self.as_readonly().get_inner(entity) - } - - /// Returns the read-only query items for the given array of [`Entity`]. - /// - /// The returned query items are in the same order as the input. - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. - /// The elements of the array do not need to be unique, unlike `get_many_mut`. - /// - /// # Examples - /// - /// ``` - /// use bevy_ecs::prelude::*; - /// use bevy_ecs::query::QueryEntityError; - /// - /// #[derive(Component, PartialEq, Debug)] - /// struct A(usize); - /// - /// let mut world = World::new(); - /// let entity_vec: Vec = (0..3).map(|i| world.spawn(A(i)).id()).collect(); - /// let entities: [Entity; 3] = entity_vec.try_into().unwrap(); - /// - /// world.spawn(A(73)); - /// - /// let mut query_state = world.query::<&A>(); - /// let query = query_state.query(&world); - /// - /// let component_values = query.get_many(entities).unwrap(); - /// - /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); - /// - /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); - /// - /// assert_eq!( - /// match query.get_many([wrong_entity]).unwrap_err() { - /// QueryEntityError::EntityDoesNotExist(error) => error.entity, - /// _ => panic!(), - /// }, - /// wrong_entity - /// ); - /// ``` - /// - /// # See also - /// - /// - [`get_many_mut`](Self::get_many_mut) to get mutable query items. - /// - [`get_many_unique`](Self::get_many_unique) to only handle unique inputs. - #[inline] - pub fn get_many( - &self, - entities: [Entity; N], - ) -> Result<[ROQueryItem<'_, 's, D>; N], QueryEntityError> { - // Note that we call a separate `*_inner` method from `get_many_mut` - // because we don't need to check for duplicates. - self.as_readonly().get_many_inner(entities) - } - - /// Returns the read-only query items for the given [`UniqueEntityArray`]. - /// - /// The returned query items are in the same order as the input. - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. - /// - /// # Examples - /// - /// ``` - /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, UniqueEntityArray, UniqueEntityVec}}; - /// - /// #[derive(Component, PartialEq, Debug)] - /// struct A(usize); - /// - /// let mut world = World::new(); - /// let entity_set: UniqueEntityVec = world.spawn_batch((0..3).map(A)).collect_set(); - /// let entity_set: UniqueEntityArray<3> = entity_set.try_into().unwrap(); - /// - /// world.spawn(A(73)); - /// - /// let mut query_state = world.query::<&A>(); - /// let query = query_state.query(&world); - /// - /// let component_values = query.get_many_unique(entity_set).unwrap(); - /// - /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); - /// - /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); - /// - /// assert_eq!( - /// match query.get_many_unique(UniqueEntityArray::from([wrong_entity])).unwrap_err() { - /// QueryEntityError::EntityDoesNotExist(error) => error.entity, - /// _ => panic!(), - /// }, - /// wrong_entity - /// ); - /// ``` - /// - /// # See also - /// - /// - [`get_many_unique_mut`](Self::get_many_mut) to get mutable query items. - /// - [`get_many`](Self::get_many) to handle inputs with duplicates. - #[inline] - pub fn get_many_unique( - &self, - entities: UniqueEntityArray, - ) -> Result<[ROQueryItem<'_, 's, D>; N], QueryEntityError> { - self.as_readonly().get_many_unique_inner(entities) - } - - /// Returns the query item for the given [`Entity`]. - /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. - /// - /// This is always guaranteed to run in `O(1)` time. - /// - /// # Example - /// - /// Here, `get_mut` is used to retrieve the exact query item of the entity specified by the `PoisonedCharacter` resource. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Resource)] - /// # struct PoisonedCharacter { character_id: Entity } - /// # #[derive(Component)] - /// # struct Health(u32); - /// # - /// fn poison_system(mut query: Query<&mut Health>, poisoned: Res) { - /// if let Ok(mut health) = query.get_mut(poisoned.character_id) { - /// health.0 -= 1; - /// } - /// } - /// # bevy_ecs::system::assert_is_system(poison_system); - /// ``` - /// - /// # See also - /// - /// - [`get`](Self::get) to get a read-only query item. - #[inline] - pub fn get_mut(&mut self, entity: Entity) -> Result, QueryEntityError> { - self.reborrow().get_inner(entity) - } - - /// Returns the query item for the given [`Entity`]. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. - /// - /// This is always guaranteed to run in `O(1)` time. - /// - /// # See also - /// - /// - [`get_mut`](Self::get_mut) to get the item using a mutable borrow of the [`Query`]. - #[inline] - pub fn get_inner(self, entity: Entity) -> Result, QueryEntityError> { - // SAFETY: system runs without conflicts with other systems. - // same-system queries have runtime borrow checks when they conflict - unsafe { - let location = self - .world - .entities() - .get(entity) - .ok_or(EntityDoesNotExistError::new(entity, self.world.entities()))?; - if !self - .state - .matched_archetypes - .contains(location.archetype_id.index()) - { - return Err(QueryEntityError::QueryDoesNotMatch( - entity, - location.archetype_id, - )); - } - let archetype = self - .world - .archetypes() - .get(location.archetype_id) - .debug_checked_unwrap(); - let mut fetch = D::init_fetch( - self.world, - &self.state.fetch_state, - self.last_run, - self.this_run, - ); - let mut filter = F::init_fetch( - self.world, - &self.state.filter_state, - self.last_run, - self.this_run, - ); - - let table = self - .world - .storages() - .tables - .get(location.table_id) - .debug_checked_unwrap(); - D::set_archetype(&mut fetch, &self.state.fetch_state, archetype, table); - F::set_archetype(&mut filter, &self.state.filter_state, archetype, table); - - if F::filter_fetch( - &self.state.filter_state, - &mut filter, - entity, - location.table_row, - ) { - Ok(D::fetch( - &self.state.fetch_state, - &mut fetch, - entity, - location.table_row, - )) - } else { - Err(QueryEntityError::QueryDoesNotMatch( - entity, - location.archetype_id, - )) - } - } - } - - /// Returns the query items for the given array of [`Entity`]. - /// - /// The returned query items are in the same order as the input. - /// In case of a nonexisting entity, duplicate entities or mismatched component, a [`QueryEntityError`] is returned instead. - /// - /// # Examples - /// - /// ``` - /// use bevy_ecs::prelude::*; - /// use bevy_ecs::query::QueryEntityError; - /// - /// #[derive(Component, PartialEq, Debug)] - /// struct A(usize); - /// - /// let mut world = World::new(); - /// - /// let entities: Vec = (0..3).map(|i| world.spawn(A(i)).id()).collect(); - /// let entities: [Entity; 3] = entities.try_into().unwrap(); - /// - /// world.spawn(A(73)); - /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); - /// let invalid_entity = world.spawn_empty().id(); - /// - /// - /// let mut query_state = world.query::<&mut A>(); - /// let mut query = query_state.query_mut(&mut world); - /// - /// let mut mutable_component_values = query.get_many_mut(entities).unwrap(); - /// - /// for mut a in &mut mutable_component_values { - /// a.0 += 5; - /// } - /// - /// let component_values = query.get_many(entities).unwrap(); - /// - /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); - /// - /// assert_eq!( - /// match query - /// .get_many_mut([wrong_entity]) - /// .unwrap_err() - /// { - /// QueryEntityError::EntityDoesNotExist(error) => error.entity, - /// _ => panic!(), - /// }, - /// wrong_entity - /// ); - /// assert_eq!( - /// match query - /// .get_many_mut([invalid_entity]) - /// .unwrap_err() - /// { - /// QueryEntityError::QueryDoesNotMatch(entity, _) => entity, - /// _ => panic!(), - /// }, - /// invalid_entity - /// ); - /// assert_eq!( - /// query - /// .get_many_mut([entities[0], entities[0]]) - /// .unwrap_err(), - /// QueryEntityError::AliasedMutability(entities[0]) - /// ); - /// ``` - /// # See also - /// - /// - [`get_many`](Self::get_many) to get read-only query items without checking for duplicate entities. - #[inline] - pub fn get_many_mut( - &mut self, - entities: [Entity; N], - ) -> Result<[D::Item<'_, 's>; N], QueryEntityError> { - self.reborrow().get_many_mut_inner(entities) - } - - /// Returns the query items for the given [`UniqueEntityArray`]. - /// - /// The returned query items are in the same order as the input. - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. - /// - /// # Examples - /// - /// ``` - /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, UniqueEntityArray, UniqueEntityVec}}; - /// - /// #[derive(Component, PartialEq, Debug)] - /// struct A(usize); - /// - /// let mut world = World::new(); - /// - /// let entity_set: UniqueEntityVec = world.spawn_batch((0..3).map(A)).collect_set(); - /// let entity_set: UniqueEntityArray<3> = entity_set.try_into().unwrap(); - /// - /// world.spawn(A(73)); - /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); - /// let invalid_entity = world.spawn_empty().id(); - /// - /// - /// let mut query_state = world.query::<&mut A>(); - /// let mut query = query_state.query_mut(&mut world); - /// - /// let mut mutable_component_values = query.get_many_unique_mut(entity_set).unwrap(); - /// - /// for mut a in &mut mutable_component_values { - /// a.0 += 5; - /// } - /// - /// let component_values = query.get_many_unique(entity_set).unwrap(); - /// - /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); - /// - /// assert_eq!( - /// match query - /// .get_many_unique_mut(UniqueEntityArray::from([wrong_entity])) - /// .unwrap_err() - /// { - /// QueryEntityError::EntityDoesNotExist(error) => error.entity, - /// _ => panic!(), - /// }, - /// wrong_entity - /// ); - /// assert_eq!( - /// match query - /// .get_many_unique_mut(UniqueEntityArray::from([invalid_entity])) - /// .unwrap_err() - /// { - /// QueryEntityError::QueryDoesNotMatch(entity, _) => entity, - /// _ => panic!(), - /// }, - /// invalid_entity - /// ); - /// ``` - /// # See also - /// - /// - [`get_many_unique`](Self::get_many) to get read-only query items. - #[inline] - pub fn get_many_unique_mut( - &mut self, - entities: UniqueEntityArray, - ) -> Result<[D::Item<'_, 's>; N], QueryEntityError> { - self.reborrow().get_many_unique_inner(entities) - } - - /// Returns the query items for the given array of [`Entity`]. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// The returned query items are in the same order as the input. - /// In case of a nonexisting entity, duplicate entities or mismatched component, a [`QueryEntityError`] is returned instead. - /// - /// # See also - /// - /// - [`get_many`](Self::get_many) to get read-only query items without checking for duplicate entities. - /// - [`get_many_mut`](Self::get_many_mut) to get items using a mutable reference. - /// - [`get_many_inner`](Self::get_many_mut_inner) to get read-only query items with the actual "inner" world lifetime. - #[inline] - pub fn get_many_mut_inner( - self, - entities: [Entity; N], - ) -> Result<[D::Item<'w, 's>; N], QueryEntityError> { - // Verify that all entities are unique - for i in 0..N { - for j in 0..i { - if entities[i] == entities[j] { - return Err(QueryEntityError::AliasedMutability(entities[i])); - } - } - } - // SAFETY: All entities are unique, so the results don't alias. - unsafe { self.get_many_impl(entities) } - } - - /// Returns the query items for the given array of [`Entity`]. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// The returned query items are in the same order as the input. - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. - /// - /// # See also - /// - /// - [`get_many`](Self::get_many) to get read-only query items without checking for duplicate entities. - /// - [`get_many_mut`](Self::get_many_mut) to get items using a mutable reference. - /// - [`get_many_mut_inner`](Self::get_many_mut_inner) to get mutable query items with the actual "inner" world lifetime. - #[inline] - pub fn get_many_inner( - self, - entities: [Entity; N], - ) -> Result<[D::Item<'w, 's>; N], QueryEntityError> - where - D: ReadOnlyQueryData, - { - // SAFETY: The query results are read-only, so they don't conflict if there are duplicate entities. - unsafe { self.get_many_impl(entities) } - } - - /// Returns the query items for the given [`UniqueEntityArray`]. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// The returned query items are in the same order as the input. - /// In case of a nonexisting entity, duplicate entities or mismatched component, a [`QueryEntityError`] is returned instead. - /// - /// # See also - /// - /// - [`get_many_unique`](Self::get_many_unique) to get read-only query items without checking for duplicate entities. - /// - [`get_many_unique_mut`](Self::get_many_unique_mut) to get items using a mutable reference. - #[inline] - pub fn get_many_unique_inner( - self, - entities: UniqueEntityArray, - ) -> Result<[D::Item<'w, 's>; N], QueryEntityError> { - // SAFETY: All entities are unique, so the results don't alias. - unsafe { self.get_many_impl(entities.into_inner()) } - } - - /// Returns the query items for the given array of [`Entity`]. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// # Safety - /// - /// The caller must ensure that the query data returned for the entities does not conflict, - /// either because they are all unique or because the data is read-only. - unsafe fn get_many_impl( - self, - entities: [Entity; N], - ) -> Result<[D::Item<'w, 's>; N], QueryEntityError> { - let mut values = [(); N].map(|_| MaybeUninit::uninit()); - - for (value, entity) in core::iter::zip(&mut values, entities) { - // SAFETY: The caller asserts that the results don't alias - let item = unsafe { self.copy_unsafe() }.get_inner(entity)?; - *value = MaybeUninit::new(item); - } - - // SAFETY: Each value has been fully initialized. - Ok(values.map(|x| unsafe { x.assume_init() })) - } - - /// Returns the query item for the given [`Entity`]. - /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. - /// - /// This is always guaranteed to run in `O(1)` time. - /// - /// # Safety - /// - /// This function makes it possible to violate Rust's aliasing guarantees. - /// You must make sure this call does not result in multiple mutable references to the same component. - /// - /// # See also - /// - /// - [`get_mut`](Self::get_mut) for the safe version. - #[inline] - pub unsafe fn get_unchecked( - &self, - entity: Entity, - ) -> Result, QueryEntityError> { - // SAFETY: The caller promises that this will not result in multiple mutable references. - unsafe { self.reborrow_unsafe() }.get_inner(entity) - } - - /// Returns a single read-only query item when there is exactly one entity matching the query. - /// - /// If the number of query items is not exactly one, a [`QuerySingleError`] is returned instead. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # use bevy_ecs::query::QuerySingleError; - /// # #[derive(Component)] - /// # struct PlayerScore(i32); - /// fn player_scoring_system(query: Query<&PlayerScore>) { - /// match query.single() { - /// Ok(PlayerScore(score)) => { - /// println!("Score: {}", score); - /// } - /// Err(QuerySingleError::NoEntities(_)) => { - /// println!("Error: There is no player!"); - /// } - /// Err(QuerySingleError::MultipleEntities(_)) => { - /// println!("Error: There is more than one player!"); - /// } - /// } - /// } - /// # bevy_ecs::system::assert_is_system(player_scoring_system); - /// ``` - /// - /// # See also - /// - /// - [`single_mut`](Self::single_mut) to get the mutable query item. - #[inline] - pub fn single(&self) -> Result, QuerySingleError> { - self.as_readonly().single_inner() - } - - /// Returns a single query item when there is exactly one entity matching the query. - /// - /// If the number of query items is not exactly one, a [`QuerySingleError`] is returned instead. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct Player; - /// # #[derive(Component)] - /// # struct Health(u32); - /// # - /// fn regenerate_player_health_system(mut query: Query<&mut Health, With>) { - /// let mut health = query.single_mut().expect("Error: Could not find a single player."); - /// health.0 += 1; - /// } - /// # bevy_ecs::system::assert_is_system(regenerate_player_health_system); - /// ``` - /// - /// # See also - /// - /// - [`single`](Self::single) to get the read-only query item. - #[inline] - pub fn single_mut(&mut self) -> Result, QuerySingleError> { - self.reborrow().single_inner() - } - - /// Returns a single query item when there is exactly one entity matching the query. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// If the number of query items is not exactly one, a [`QuerySingleError`] is returned instead. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct Player; - /// # #[derive(Component)] - /// # struct Health(u32); - /// # - /// fn regenerate_player_health_system(query: Query<&mut Health, With>) { - /// let mut health = query.single_inner().expect("Error: Could not find a single player."); - /// health.0 += 1; - /// } - /// # bevy_ecs::system::assert_is_system(regenerate_player_health_system); - /// ``` - /// - /// # See also - /// - /// - [`single`](Self::single) to get the read-only query item. - /// - [`single_mut`](Self::single_mut) to get the mutable query item. - /// - [`single_inner`](Self::single_inner) for the panicking version. - #[inline] - pub fn single_inner(self) -> Result, QuerySingleError> { - let mut query = self.into_iter(); - let first = query.next(); - let extra = query.next().is_some(); - - match (first, extra) { - (Some(r), false) => Ok(r), - (None, _) => Err(QuerySingleError::NoEntities(DebugName::type_name::())), - (Some(_), _) => Err(QuerySingleError::MultipleEntities(DebugName::type_name::< - Self, - >())), - } - } - - /// Returns `true` if there are no query items. - /// - /// This is equivalent to `self.iter().next().is_none()`, and thus the worst case runtime will be `O(n)` - /// where `n` is the number of *potential* matches. This can be notably expensive for queries that rely - /// on non-archetypal filters such as [`Added`], [`Changed`] or [`Spawned`] which must individually check - /// each query result for a match. - /// - /// # Example - /// - /// Here, the score is increased only if an entity with a `Player` component is present in the world: - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct Player; - /// # #[derive(Resource)] - /// # struct Score(u32); - /// fn update_score_system(query: Query<(), With>, mut score: ResMut) { - /// if !query.is_empty() { - /// score.0 += 1; - /// } - /// } - /// # bevy_ecs::system::assert_is_system(update_score_system); - /// ``` - /// - /// [`Added`]: crate::query::Added - /// [`Changed`]: crate::query::Changed - /// [`Spawned`]: crate::query::Spawned - #[inline] - pub fn is_empty(&self) -> bool { - self.as_nop().iter().next().is_none() - } - - /// Returns `true` if the given [`Entity`] matches the query. - /// - /// This is always guaranteed to run in `O(1)` time. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct InRange; - /// # - /// # #[derive(Resource)] - /// # struct Target { - /// # entity: Entity, - /// # } - /// # - /// fn targeting_system(in_range_query: Query<&InRange>, target: Res) { - /// if in_range_query.contains(target.entity) { - /// println!("Bam!") - /// } - /// } - /// # bevy_ecs::system::assert_is_system(targeting_system); - /// ``` - #[inline] - pub fn contains(&self, entity: Entity) -> bool { - self.as_nop().get(entity).is_ok() - } - - /// Returns a [`QueryLens`] that can be used to construct a new [`Query`] giving more - /// restrictive access to the entities matched by the current query. - /// - /// A transmute is valid only if `NewD` has a subset of the read, write, and required access - /// of the current query. A precise description of the access required by each parameter - /// type is given in the table below, but typical uses are to: - /// * Remove components, e.g. `Query<(&A, &B)>` to `Query<&A>`. - /// * Retrieve an existing component with reduced or equal access, e.g. `Query<&mut A>` to `Query<&A>` - /// or `Query<&T>` to `Query>`. - /// * Add parameters with no new access, for example adding an `Entity` parameter. - /// - /// Note that since filter terms are dropped, non-archetypal filters like - /// [`Added`], [`Changed`] and [`Spawned`] will not be respected. To maintain or change filter - /// terms see [`Self::transmute_lens_filtered`]. - /// - /// |`QueryData` parameter type|Access required| - /// |----|----| - /// |[`Entity`], [`EntityLocation`], [`SpawnDetails`], [`&Archetype`], [`Has`], [`PhantomData`]|No access| - /// |[`EntityMut`]|Read and write access to all components, but no required access| - /// |[`EntityRef`]|Read access to all components, but no required access| - /// |`&T`, [`Ref`]|Read and required access to `T`| - /// |`&mut T`, [`Mut`]|Read, write and required access to `T`| - /// |[`Option`], [`AnyOf<(D, ...)>`]|Read and write access to `T`, but no required access| - /// |Tuples of query data and
`#[derive(QueryData)]` structs|The union of the access of their subqueries| - /// |[`FilteredEntityRef`], [`FilteredEntityMut`]|Determined by the [`QueryBuilder`] used to construct them. Any query can be transmuted to them, and they will receive the access of the source query. When combined with other `QueryData`, they will receive any access of the source query that does not conflict with the other data| - /// - /// `transmute_lens` drops filter terms, but [`Self::transmute_lens_filtered`] supports returning a [`QueryLens`] with a new - /// filter type - the access required by filter parameters are as follows. - /// - /// |`QueryFilter` parameter type|Access required| - /// |----|----| - /// |[`Added`], [`Changed`]|Read and required access to `T`| - /// |[`With`], [`Without`]|No access| - /// |[`Or<(T, ...)>`]|Read access of the subqueries, but no required access| - /// |Tuples of query filters and `#[derive(QueryFilter)]` structs|The union of the access of their subqueries| - /// - /// [`Added`]: crate::query::Added - /// [`Added`]: crate::query::Added - /// [`AnyOf<(D, ...)>`]: crate::query::AnyOf - /// [`&Archetype`]: crate::archetype::Archetype - /// [`Changed`]: crate::query::Changed - /// [`Changed`]: crate::query::Changed - /// [`EntityMut`]: crate::world::EntityMut - /// [`EntityLocation`]: crate::entity::EntityLocation - /// [`EntityRef`]: crate::world::EntityRef - /// [`FilteredEntityRef`]: crate::world::FilteredEntityRef - /// [`FilteredEntityMut`]: crate::world::FilteredEntityMut - /// [`Has`]: crate::query::Has - /// [`Mut`]: crate::world::Mut - /// [`Or<(T, ...)>`]: crate::query::Or - /// [`QueryBuilder`]: crate::query::QueryBuilder - /// [`Ref`]: crate::world::Ref - /// [`SpawnDetails`]: crate::query::SpawnDetails - /// [`Spawned`]: crate::query::Spawned - /// [`With`]: crate::query::With - /// [`Without`]: crate::query::Without - /// - /// ## Panics - /// - /// This will panic if the access required by `NewD` is not a subset of that required by - /// the original fetch `D`. - /// - /// ## Example - /// - /// ```rust - /// # use bevy_ecs::prelude::*; - /// # use bevy_ecs::system::QueryLens; - /// # - /// # #[derive(Component)] - /// # struct A(usize); - /// # - /// # #[derive(Component)] - /// # struct B(usize); - /// # - /// # let mut world = World::new(); - /// # - /// # world.spawn((A(10), B(5))); - /// # - /// fn reusable_function(lens: &mut QueryLens<&A>) { - /// assert_eq!(lens.query().single().unwrap().0, 10); - /// } - /// - /// // We can use the function in a system that takes the exact query. - /// fn system_1(mut query: Query<&A>) { - /// reusable_function(&mut query.as_query_lens()); - /// } - /// - /// // We can also use it with a query that does not match exactly - /// // by transmuting it. - /// fn system_2(mut query: Query<(&mut A, &B)>) { - /// let mut lens = query.transmute_lens::<&A>(); - /// reusable_function(&mut lens); - /// } - /// - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems((system_1, system_2)); - /// # schedule.run(&mut world); - /// ``` - /// - /// ### Examples of valid transmutes - /// - /// ```rust - /// # use bevy_ecs::{ - /// # prelude::*, - /// # archetype::Archetype, - /// # entity::EntityLocation, - /// # query::{QueryData, QueryFilter}, - /// # world::{FilteredEntityMut, FilteredEntityRef}, - /// # }; - /// # use std::marker::PhantomData; - /// # - /// # fn assert_valid_transmute() { - /// # assert_valid_transmute_filtered::(); - /// # } - /// # - /// # fn assert_valid_transmute_filtered() { - /// # let mut world = World::new(); - /// # // Make sure all components in the new query are initialized - /// # let state = world.query_filtered::(); - /// # let state = world.query_filtered::(); - /// # state.transmute_filtered::(&world); - /// # } - /// # - /// # #[derive(Component)] - /// # struct T; - /// # - /// # #[derive(Component)] - /// # struct U; - /// # - /// # #[derive(Component)] - /// # struct V; - /// # - /// // `&mut T` and `Mut` access the same data and can be transmuted to each other, - /// // `&T` and `Ref` access the same data and can be transmuted to each other, - /// // and mutable versions can be transmuted to read-only versions - /// assert_valid_transmute::<&mut T, &T>(); - /// assert_valid_transmute::<&mut T, Mut>(); - /// assert_valid_transmute::, &mut T>(); - /// assert_valid_transmute::<&T, Ref>(); - /// assert_valid_transmute::, &T>(); - /// - /// // The structure can be rearranged, or subqueries dropped - /// assert_valid_transmute::<(&T, &U), &T>(); - /// assert_valid_transmute::<((&T, &U), &V), (&T, (&U, &V))>(); - /// assert_valid_transmute::, (Option<&T>, Option<&U>)>(); - /// - /// // Queries with no access can be freely added - /// assert_valid_transmute::< - /// &T, - /// (&T, Entity, EntityLocation, &Archetype, Has, PhantomData), - /// >(); - /// - /// // Required access can be transmuted to optional, - /// // and optional access can be transmuted to other optional access - /// assert_valid_transmute::<&T, Option<&T>>(); - /// assert_valid_transmute::, Option<&T>>(); - /// // Note that removing subqueries from `AnyOf` will result - /// // in an `AnyOf` where all subqueries can yield `None`! - /// assert_valid_transmute::, AnyOf<(&T, &U)>>(); - /// assert_valid_transmute::>(); - /// - /// // Anything can be transmuted to `FilteredEntityRef` or `FilteredEntityMut` - /// // This will create a `FilteredEntityMut` that only has read access to `T` - /// assert_valid_transmute::<&T, FilteredEntityMut>(); - /// // This will create a `FilteredEntityMut` that has no access to `T`, - /// // read access to `U`, and write access to `V`. - /// assert_valid_transmute::<(&mut T, &mut U, &mut V), (&mut T, &U, FilteredEntityMut)>(); - /// - /// // `Added` and `Changed` filters have the same access as `&T` data - /// // Remember that they are only evaluated on the transmuted query, not the original query! - /// assert_valid_transmute_filtered::, &T, ()>(); - /// assert_valid_transmute_filtered::<&mut T, (), &T, Added>(); - /// // Nested inside of an `Or` filter, they have the same access as `Option<&T>`. - /// assert_valid_transmute_filtered::, (), Entity, Or<(Changed, With)>>(); - /// ``` - #[track_caller] - pub fn transmute_lens(&mut self) -> QueryLens<'_, NewD> { - self.transmute_lens_filtered::() - } - - /// Returns a [`QueryLens`] that can be used to construct a new `Query` giving more restrictive - /// access to the entities matched by the current query. - /// - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// See [`Self::transmute_lens`] for a description of allowed transmutes. - /// - /// ## Panics - /// - /// This will panic if `NewD` is not a subset of the original fetch `D` - /// - /// ## Example - /// - /// ```rust - /// # use bevy_ecs::prelude::*; - /// # use bevy_ecs::system::QueryLens; - /// # - /// # #[derive(Component)] - /// # struct A(usize); - /// # - /// # #[derive(Component)] - /// # struct B(usize); - /// # - /// # let mut world = World::new(); - /// # - /// # world.spawn((A(10), B(5))); - /// # - /// fn reusable_function(mut lens: QueryLens<&A>) { - /// assert_eq!(lens.query().single().unwrap().0, 10); - /// } - /// - /// // We can use the function in a system that takes the exact query. - /// fn system_1(query: Query<&A>) { - /// reusable_function(query.into_query_lens()); - /// } - /// - /// // We can also use it with a query that does not match exactly - /// // by transmuting it. - /// fn system_2(query: Query<(&mut A, &B)>) { - /// let mut lens = query.transmute_lens_inner::<&A>(); - /// reusable_function(lens); - /// } - /// - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems((system_1, system_2)); - /// # schedule.run(&mut world); - /// ``` - /// - /// # See also - /// - /// - [`transmute_lens`](Self::transmute_lens) to convert to a lens using a mutable borrow of the [`Query`]. - #[track_caller] - pub fn transmute_lens_inner(self) -> QueryLens<'w, NewD> { - self.transmute_lens_filtered_inner::() - } - - /// Equivalent to [`Self::transmute_lens`] but also includes a [`QueryFilter`] type. - /// - /// See [`Self::transmute_lens`] for a description of allowed transmutes. - /// - /// Note that the lens will iterate the same tables and archetypes as the original query. This means that - /// additional archetypal query terms like [`With`](crate::query::With) and [`Without`](crate::query::Without) - /// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added), - /// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will only be respected if they - /// are in the type signature. - #[track_caller] - pub fn transmute_lens_filtered( - &mut self, - ) -> QueryLens<'_, NewD, NewF> { - self.reborrow().transmute_lens_filtered_inner() - } - - /// Equivalent to [`Self::transmute_lens_inner`] but also includes a [`QueryFilter`] type. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// See [`Self::transmute_lens`] for a description of allowed transmutes. - /// - /// Note that the lens will iterate the same tables and archetypes as the original query. This means that - /// additional archetypal query terms like [`With`](crate::query::With) and [`Without`](crate::query::Without) - /// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added), - /// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will only be respected if they - /// are in the type signature. - /// - /// # See also - /// - /// - [`transmute_lens_filtered`](Self::transmute_lens_filtered) to convert to a lens using a mutable borrow of the [`Query`]. - #[track_caller] - pub fn transmute_lens_filtered_inner( - self, - ) -> QueryLens<'w, NewD, NewF> { - let state = self.state.transmute_filtered::(self.world); - QueryLens { - world: self.world, - state, - last_run: self.last_run, - this_run: self.this_run, - } - } - - /// Gets a [`QueryLens`] with the same accesses as the existing query - pub fn as_query_lens(&mut self) -> QueryLens<'_, D> { - self.transmute_lens() - } - - /// Gets a [`QueryLens`] with the same accesses as the existing query - /// - /// # See also - /// - /// - [`as_query_lens`](Self::as_query_lens) to convert to a lens using a mutable borrow of the [`Query`]. - pub fn into_query_lens(self) -> QueryLens<'w, D> { - self.transmute_lens_inner() - } - - /// Returns a [`QueryLens`] that can be used to get a query with the combined fetch. - /// - /// For example, this can take a `Query<&A>` and a `Query<&B>` and return a `Query<(&A, &B)>`. - /// The returned query will only return items with both `A` and `B`. Note that since filters - /// are dropped, non-archetypal filters like `Added`, `Changed` and `Spawned` will not be respected. - /// To maintain or change filter terms see `Self::join_filtered`. - /// - /// ## Example - /// - /// ```rust - /// # use bevy_ecs::prelude::*; - /// # use bevy_ecs::system::QueryLens; - /// # - /// # #[derive(Component)] - /// # struct Transform; - /// # - /// # #[derive(Component)] - /// # struct Player; - /// # - /// # #[derive(Component)] - /// # struct Enemy; - /// # - /// # let mut world = World::default(); - /// # world.spawn((Transform, Player)); - /// # world.spawn((Transform, Enemy)); - /// - /// fn system( - /// mut transforms: Query<&Transform>, - /// mut players: Query<&Player>, - /// mut enemies: Query<&Enemy> - /// ) { - /// let mut players_transforms: QueryLens<(&Transform, &Player)> = transforms.join(&mut players); - /// for (transform, player) in &players_transforms.query() { - /// // do something with a and b - /// } - /// - /// let mut enemies_transforms: QueryLens<(&Transform, &Enemy)> = transforms.join(&mut enemies); - /// for (transform, enemy) in &enemies_transforms.query() { - /// // do something with a and b - /// } - /// } - /// - /// # let mut schedule = Schedule::default(); - /// # schedule.add_systems(system); - /// # schedule.run(&mut world); - /// ``` - /// ## Panics - /// - /// This will panic if `NewD` is not a subset of the union of the original fetch `Q` and `OtherD`. - /// - /// ## Allowed Transmutes - /// - /// Like `transmute_lens` the query terms can be changed with some restrictions. - /// See [`Self::transmute_lens`] for more details. - pub fn join<'a, OtherD: QueryData, NewD: QueryData>( - &'a mut self, - other: &'a mut Query, - ) -> QueryLens<'a, NewD> { - self.join_filtered(other) - } - - /// Returns a [`QueryLens`] that can be used to get a query with the combined fetch. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// For example, this can take a `Query<&A>` and a `Query<&B>` and return a `Query<(&A, &B)>`. - /// The returned query will only return items with both `A` and `B`. Note that since filters - /// are dropped, non-archetypal filters like `Added`, `Changed` and `Spawned` will not be respected. - /// To maintain or change filter terms see `Self::join_filtered`. - /// - /// ## Panics - /// - /// This will panic if `NewD` is not a subset of the union of the original fetch `Q` and `OtherD`. - /// - /// ## Allowed Transmutes - /// - /// Like `transmute_lens` the query terms can be changed with some restrictions. - /// See [`Self::transmute_lens`] for more details. - /// - /// # See also - /// - /// - [`join`](Self::join) to join using a mutable borrow of the [`Query`]. - pub fn join_inner( - self, - other: Query<'w, '_, OtherD>, - ) -> QueryLens<'w, NewD> { - self.join_filtered_inner(other) - } - - /// Equivalent to [`Self::join`] but also includes a [`QueryFilter`] type. - /// - /// Note that the lens with iterate a subset of the original queries' tables - /// and archetypes. This means that additional archetypal query terms like - /// `With` and `Without` will not necessarily be respected and non-archetypal - /// terms like `Added`, `Changed` and `Spawned` will only be respected if they - /// are in the type signature. - pub fn join_filtered< - 'a, - OtherD: QueryData, - OtherF: QueryFilter, - NewD: QueryData, - NewF: QueryFilter, - >( - &'a mut self, - other: &'a mut Query, - ) -> QueryLens<'a, NewD, NewF> { - self.reborrow().join_filtered_inner(other.reborrow()) - } - - /// Equivalent to [`Self::join_inner`] but also includes a [`QueryFilter`] type. - /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. - /// - /// Note that the lens with iterate a subset of the original queries' tables - /// and archetypes. This means that additional archetypal query terms like - /// `With` and `Without` will not necessarily be respected and non-archetypal - /// terms like `Added`, `Changed` and `Spawned` will only be respected if they - /// are in the type signature. - /// - /// # See also - /// - /// - [`join_filtered`](Self::join_filtered) to join using a mutable borrow of the [`Query`]. - pub fn join_filtered_inner< - OtherD: QueryData, - OtherF: QueryFilter, - NewD: QueryData, - NewF: QueryFilter, - >( - self, - other: Query<'w, '_, OtherD, OtherF>, - ) -> QueryLens<'w, NewD, NewF> { - let state = self - .state - .join_filtered::(self.world, other.state); - QueryLens { - world: self.world, - state, - last_run: self.last_run, - this_run: self.this_run, - } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Query<'w, 's, D, F> { - type Item = D::Item<'w, 's>; - type IntoIter = QueryIter<'w, 's, D, F>; - - fn into_iter(self) -> Self::IntoIter { - // SAFETY: - // - `self.world` has permission to access the required components. - // - We consume the query, so mutable queries cannot alias. - // Read-only queries are `Copy`, but may alias themselves. - unsafe { QueryIter::new(self.world, self.state, self.last_run, self.this_run) } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'w Query<'_, 's, D, F> { - type Item = ROQueryItem<'w, 's, D>; - type IntoIter = QueryIter<'w, 's, D::ReadOnly, F>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'w mut Query<'_, 's, D, F> { - type Item = D::Item<'w, 's>; - type IntoIter = QueryIter<'w, 's, D, F>; - - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} - -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter> Query<'w, 's, D, F> { - /// Returns an [`Iterator`] over the query items, with the actual "inner" world lifetime. - /// - /// This can only return immutable data (mutable data will be cast to an immutable form). - /// See [`Self::iter_mut`] for queries that contain at least one mutable component. - /// - /// # Example - /// - /// Here, the `report_names_system` iterates over the `Player` component of every entity - /// that contains it: - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct Player { name: String } - /// # - /// fn report_names_system(query: Query<&Player>) { - /// for player in &query { - /// println!("Say hello to {}!", player.name); - /// } - /// } - /// # bevy_ecs::system::assert_is_system(report_names_system); - /// ``` - #[inline] - pub fn iter_inner(&self) -> QueryIter<'w, 's, D::ReadOnly, F> { - (*self).into_iter() - } -} - -/// Type returned from [`Query::transmute_lens`] containing the new [`QueryState`]. -/// -/// Call [`query`](QueryLens::query) or [`into`](Into::into) to construct the resulting [`Query`] -pub struct QueryLens<'w, Q: QueryData, F: QueryFilter = ()> { - world: UnsafeWorldCell<'w>, - state: QueryState, - last_run: Tick, - this_run: Tick, -} - -impl<'w, Q: QueryData, F: QueryFilter> QueryLens<'w, Q, F> { - /// Create a [`Query`] from the underlying [`QueryState`]. - pub fn query(&mut self) -> Query<'_, '_, Q, F> { - Query { - world: self.world, - state: &self.state, - last_run: self.last_run, - this_run: self.this_run, - } - } -} - -impl<'w, Q: ReadOnlyQueryData, F: QueryFilter> QueryLens<'w, Q, F> { - /// Create a [`Query`] from the underlying [`QueryState`]. - /// This returns results with the actual "inner" world lifetime, - /// so it may only be used with read-only queries to prevent mutable aliasing. - pub fn query_inner(&self) -> Query<'w, '_, Q, F> { - Query { - world: self.world, - state: &self.state, - last_run: self.last_run, - this_run: self.this_run, - } - } -} - -impl<'w, 's, Q: QueryData, F: QueryFilter> From<&'s mut QueryLens<'w, Q, F>> - for Query<'s, 's, Q, F> -{ - fn from(value: &'s mut QueryLens<'w, Q, F>) -> Query<'s, 's, Q, F> { - value.query() - } -} - -impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>> - for QueryLens<'q, Q, F> -{ - fn from(value: &'q mut Query<'w, '_, Q, F>) -> QueryLens<'q, Q, F> { - value.transmute_lens_filtered() - } -} - -/// [System parameter] that provides access to single entity's components, much like [`Query::single`]/[`Query::single_mut`]. -/// -/// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists. -/// This will cause the system to be skipped, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError). -/// -/// Use [`Option>`] instead if zero or one matching entities can exist. -/// -/// See [`Query`] for more details. -/// -/// [System parameter]: crate::system::SystemParam -/// -/// # Example -/// ``` -/// # use bevy_ecs::prelude::*; -/// #[derive(Component)] -/// struct Boss { -/// health: f32 -/// }; -/// -/// fn hurt_boss(mut boss: Single<&mut Boss>) { -/// boss.health -= 4.0; -/// } -/// ``` -/// Note that because [`Single`] implements [`Deref`] and [`DerefMut`], methods and fields like `health` can be accessed directly. -/// You can also access the underlying data manually, by calling `.deref`/`.deref_mut`, or by using the `*` operator. -pub struct Single<'w, 's, D: QueryData, F: QueryFilter = ()> { - pub(crate) item: D::Item<'w, 's>, - pub(crate) _filter: PhantomData, -} - -impl<'w, 's, D: QueryData, F: QueryFilter> Deref for Single<'w, 's, D, F> { - type Target = D::Item<'w, 's>; - - fn deref(&self) -> &Self::Target { - &self.item - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> DerefMut for Single<'w, 's, D, F> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.item - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> Single<'w, 's, D, F> { - /// Returns the inner item with ownership. - pub fn into_inner(self) -> D::Item<'w, 's> { - self.item - } -} - -/// [System parameter] that works very much like [`Query`] except it always contains at least one matching entity. -/// -/// This [`SystemParam`](crate::system::SystemParam) fails validation if no matching entities exist. -/// This will cause the system to be skipped, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError). -/// -/// Much like [`Query::is_empty`] the worst case runtime will be `O(n)` where `n` is the number of *potential* matches. -/// This can be notably expensive for queries that rely on non-archetypal filters such as [`Added`](crate::query::Added), -/// [`Changed`](crate::query::Changed) of [`Spawned`](crate::query::Spawned) which must individually check each query -/// result for a match. -/// -/// See [`Query`] for more details. -/// -/// [System parameter]: crate::system::SystemParam -pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = ()>(pub(crate) Query<'w, 's, D, F>); - -impl<'w, 's, D: QueryData, F: QueryFilter> Deref for Populated<'w, 's, D, F> { - type Target = Query<'w, 's, D, F>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Populated<'_, '_, D, F> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> Populated<'w, 's, D, F> { - /// Returns the inner item with ownership. - pub fn into_inner(self) -> Query<'w, 's, D, F> { - self.0 - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Populated<'w, 's, D, F> { - type Item = as IntoIterator>::Item; - - type IntoIter = as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a, 'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'a Populated<'w, 's, D, F> { - type Item = <&'a Query<'w, 's, D, F> as IntoIterator>::Item; - - type IntoIter = <&'a Query<'w, 's, D, F> as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.deref().into_iter() - } -} - -impl<'a, 'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'a mut Populated<'w, 's, D, F> { - type Item = <&'a mut Query<'w, 's, D, F> as IntoIterator>::Item; - - type IntoIter = <&'a mut Query<'w, 's, D, F> as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.deref_mut().into_iter() - } -} - -#[cfg(test)] -mod tests { - use crate::{prelude::*, query::QueryEntityError}; - use alloc::vec::Vec; - - #[test] - fn get_many_uniqueness() { - let mut world = World::new(); - - let entities: Vec = (0..10).map(|_| world.spawn_empty().id()).collect(); - - let mut query_state = world.query::(); - - // It's best to test get_many_mut_inner directly, as it is shared - // We don't care about aliased mutability for the read-only equivalent - - // SAFETY: Query does not access world data. - assert!(query_state - .query_mut(&mut world) - .get_many_mut_inner::<10>(entities.clone().try_into().unwrap()) - .is_ok()); - - assert_eq!( - query_state - .query_mut(&mut world) - .get_many_mut_inner([entities[0], entities[0]]) - .unwrap_err(), - QueryEntityError::AliasedMutability(entities[0]) - ); - - assert_eq!( - query_state - .query_mut(&mut world) - .get_many_mut_inner([entities[0], entities[1], entities[0]]) - .unwrap_err(), - QueryEntityError::AliasedMutability(entities[0]) - ); - - assert_eq!( - query_state - .query_mut(&mut world) - .get_many_mut_inner([entities[9], entities[9]]) - .unwrap_err(), - QueryEntityError::AliasedMutability(entities[9]) - ); - } -} diff --git a/src/system/schedule_system.rs b/src/system/schedule_system.rs deleted file mode 100644 index e2a853d..0000000 --- a/src/system/schedule_system.rs +++ /dev/null @@ -1,211 +0,0 @@ -use bevy_utils::prelude::DebugName; - -use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, - error::Result, - query::FilteredAccessSet, - system::{input::SystemIn, BoxedSystem, RunSystemError, System, SystemInput}, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World}, -}; - -use super::{IntoSystem, SystemParamValidationError, SystemStateFlags}; - -/// See [`IntoSystem::with_input`] for details. -pub struct WithInputWrapper -where - for<'i> S: System = &'i mut T>>, - T: Send + Sync + 'static, -{ - system: S, - value: T, -} - -impl WithInputWrapper -where - for<'i> S: System = &'i mut T>>, - T: Send + Sync + 'static, -{ - /// Wraps the given system with the given input value. - pub fn new(system: impl IntoSystem, value: T) -> Self { - Self { - system: IntoSystem::into_system(system), - value, - } - } - - /// Returns a reference to the input value. - pub fn value(&self) -> &T { - &self.value - } - - /// Returns a mutable reference to the input value. - pub fn value_mut(&mut self) -> &mut T { - &mut self.value - } -} - -impl System for WithInputWrapper -where - for<'i> S: System = &'i mut T>>, - T: Send + Sync + 'static, -{ - type In = (); - type Out = S::Out; - - fn name(&self) -> DebugName { - self.system.name() - } - - #[inline] - fn flags(&self) -> SystemStateFlags { - self.system.flags() - } - - unsafe fn run_unsafe( - &mut self, - _input: SystemIn<'_, Self>, - world: UnsafeWorldCell, - ) -> Result { - self.system.run_unsafe(&mut self.value, world) - } - - #[cfg(feature = "hotpatching")] - #[inline] - fn refresh_hotpatch(&mut self) { - self.system.refresh_hotpatch(); - } - - fn apply_deferred(&mut self, world: &mut World) { - self.system.apply_deferred(world); - } - - fn queue_deferred(&mut self, world: DeferredWorld) { - self.system.queue_deferred(world); - } - - unsafe fn validate_param_unsafe( - &mut self, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - self.system.validate_param_unsafe(world) - } - - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { - self.system.initialize(world) - } - - fn check_change_tick(&mut self, check: CheckChangeTicks) { - self.system.check_change_tick(check); - } - - fn get_last_run(&self) -> Tick { - self.system.get_last_run() - } - - fn set_last_run(&mut self, last_run: Tick) { - self.system.set_last_run(last_run); - } -} - -/// Constructed in [`IntoSystem::with_input_from`]. -pub struct WithInputFromWrapper { - system: S, - value: Option, -} - -impl WithInputFromWrapper -where - for<'i> S: System = &'i mut T>>, - T: Send + Sync + 'static, -{ - /// Wraps the given system. - pub fn new(system: impl IntoSystem) -> Self { - Self { - system: IntoSystem::into_system(system), - value: None, - } - } - - /// Returns a reference to the input value, if it has been initialized. - pub fn value(&self) -> Option<&T> { - self.value.as_ref() - } - - /// Returns a mutable reference to the input value, if it has been initialized. - pub fn value_mut(&mut self) -> Option<&mut T> { - self.value.as_mut() - } -} - -impl System for WithInputFromWrapper -where - for<'i> S: System = &'i mut T>>, - T: FromWorld + Send + Sync + 'static, -{ - type In = (); - type Out = S::Out; - - fn name(&self) -> DebugName { - self.system.name() - } - - #[inline] - fn flags(&self) -> SystemStateFlags { - self.system.flags() - } - - unsafe fn run_unsafe( - &mut self, - _input: SystemIn<'_, Self>, - world: UnsafeWorldCell, - ) -> Result { - let value = self - .value - .as_mut() - .expect("System input value was not found. Did you forget to initialize the system before running it?"); - self.system.run_unsafe(value, world) - } - - #[cfg(feature = "hotpatching")] - #[inline] - fn refresh_hotpatch(&mut self) { - self.system.refresh_hotpatch(); - } - - fn apply_deferred(&mut self, world: &mut World) { - self.system.apply_deferred(world); - } - - fn queue_deferred(&mut self, world: DeferredWorld) { - self.system.queue_deferred(world); - } - - unsafe fn validate_param_unsafe( - &mut self, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - self.system.validate_param_unsafe(world) - } - - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { - if self.value.is_none() { - self.value = Some(T::from_world(world)); - } - self.system.initialize(world) - } - - fn check_change_tick(&mut self, check: CheckChangeTicks) { - self.system.check_change_tick(check); - } - - fn get_last_run(&self) -> Tick { - self.system.get_last_run() - } - - fn set_last_run(&mut self, last_run: Tick) { - self.system.set_last_run(last_run); - } -} - -/// Type alias for a `BoxedSystem` that a `Schedule` can store. -pub type ScheduleSystem = BoxedSystem<(), ()>; diff --git a/src/system/system.rs b/src/system/system.rs deleted file mode 100644 index aad37c0..0000000 --- a/src/system/system.rs +++ /dev/null @@ -1,529 +0,0 @@ -#![expect( - clippy::module_inception, - reason = "This instance of module inception is being discussed; see #17353." -)] -use bevy_utils::prelude::DebugName; -use bitflags::bitflags; -use core::fmt::{Debug, Display}; -use log::warn; - -use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, - error::BevyError, - query::FilteredAccessSet, - schedule::InternedSystemSet, - system::{input::SystemInput, SystemIn}, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, -}; - -use alloc::{boxed::Box, vec::Vec}; -use core::any::{Any, TypeId}; - -use super::{IntoSystem, SystemParamValidationError}; - -bitflags! { - /// Bitflags representing system states and requirements. - #[derive(Clone, Copy, PartialEq, Eq, Hash)] - pub struct SystemStateFlags: u8 { - /// Set if system cannot be sent across threads - const NON_SEND = 1 << 0; - /// Set if system requires exclusive World access - const EXCLUSIVE = 1 << 1; - /// Set if system has deferred buffers. - const DEFERRED = 1 << 2; - } -} -/// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule) -/// -/// Systems are functions with all arguments implementing -/// [`SystemParam`](crate::system::SystemParam). -/// -/// Systems are added to an application using `App::add_systems(Update, my_system)` -/// or similar methods, and will generally run once per pass of the main loop. -/// -/// Systems are executed in parallel, in opportunistic order; data access is managed automatically. -/// It's possible to specify explicit execution order between specific systems, -/// see [`IntoScheduleConfigs`](crate::schedule::IntoScheduleConfigs). -#[diagnostic::on_unimplemented(message = "`{Self}` is not a system", label = "invalid system")] -pub trait System: Send + Sync + 'static { - /// The system's input. - type In: SystemInput; - /// The system's output. - type Out; - - /// Returns the system's name. - fn name(&self) -> DebugName; - /// Returns the [`TypeId`] of the underlying system type. - #[inline] - fn type_id(&self) -> TypeId { - TypeId::of::() - } - - /// Returns the [`SystemStateFlags`] of the system. - fn flags(&self) -> SystemStateFlags; - - /// Returns true if the system is [`Send`]. - #[inline] - fn is_send(&self) -> bool { - !self.flags().intersects(SystemStateFlags::NON_SEND) - } - - /// Returns true if the system must be run exclusively. - #[inline] - fn is_exclusive(&self) -> bool { - self.flags().intersects(SystemStateFlags::EXCLUSIVE) - } - - /// Returns true if system has deferred buffers. - #[inline] - fn has_deferred(&self) -> bool { - self.flags().intersects(SystemStateFlags::DEFERRED) - } - - /// Runs the system with the given input in the world. Unlike [`System::run`], this function - /// can be called in parallel with other systems and may break Rust's aliasing rules - /// if used incorrectly, making it unsafe to call. - /// - /// Unlike [`System::run`], this will not apply deferred parameters, which must be independently - /// applied by calling [`System::apply_deferred`] at later point in time. - /// - /// # Safety - /// - /// - The caller must ensure that [`world`](UnsafeWorldCell) has permission to access any world data - /// registered in the access returned from [`System::initialize`]. There must be no conflicting - /// simultaneous accesses while the system is running. - /// - If [`System::is_exclusive`] returns `true`, then it must be valid to call - /// [`UnsafeWorldCell::world_mut`] on `world`. - unsafe fn run_unsafe( - &mut self, - input: SystemIn<'_, Self>, - world: UnsafeWorldCell, - ) -> Result; - - /// Refresh the inner pointer based on the latest hot patch jump table - #[cfg(feature = "hotpatching")] - fn refresh_hotpatch(&mut self); - - /// Runs the system with the given input in the world. - /// - /// For [read-only](ReadOnlySystem) systems, see [`run_readonly`], which can be called using `&World`. - /// - /// Unlike [`System::run_unsafe`], this will apply deferred parameters *immediately*. - /// - /// [`run_readonly`]: ReadOnlySystem::run_readonly - fn run( - &mut self, - input: SystemIn<'_, Self>, - world: &mut World, - ) -> Result { - let ret = self.run_without_applying_deferred(input, world)?; - self.apply_deferred(world); - Ok(ret) - } - - /// Runs the system with the given input in the world. - /// - /// [`run_readonly`]: ReadOnlySystem::run_readonly - fn run_without_applying_deferred( - &mut self, - input: SystemIn<'_, Self>, - world: &mut World, - ) -> Result { - let world_cell = world.as_unsafe_world_cell(); - // SAFETY: - // - We have exclusive access to the entire world. - unsafe { self.validate_param_unsafe(world_cell) }?; - // SAFETY: - // - We have exclusive access to the entire world. - // - `update_archetype_component_access` has been called. - unsafe { self.run_unsafe(input, world_cell) } - } - - /// Applies any [`Deferred`](crate::system::Deferred) system parameters (or other system buffers) of this system to the world. - /// - /// This is where [`Commands`](crate::system::Commands) get applied. - fn apply_deferred(&mut self, world: &mut World); - - /// Enqueues any [`Deferred`](crate::system::Deferred) system parameters (or other system buffers) - /// of this system into the world's command buffer. - fn queue_deferred(&mut self, world: DeferredWorld); - - /// Validates that all parameters can be acquired and that system can run without panic. - /// Built-in executors use this to prevent invalid systems from running. - /// - /// However calling and respecting [`System::validate_param_unsafe`] or its safe variant - /// is not a strict requirement, both [`System::run`] and [`System::run_unsafe`] - /// should provide their own safety mechanism to prevent undefined behavior. - /// - /// This method has to be called directly before [`System::run_unsafe`] with no other (relevant) - /// world mutations in between. Otherwise, while it won't lead to any undefined behavior, - /// the validity of the param may change. - /// - /// # Safety - /// - /// - The caller must ensure that [`world`](UnsafeWorldCell) has permission to access any world data - /// registered in the access returned from [`System::initialize`]. There must be no conflicting - /// simultaneous accesses while the system is running. - unsafe fn validate_param_unsafe( - &mut self, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError>; - - /// Safe version of [`System::validate_param_unsafe`]. - /// that runs on exclusive, single-threaded `world` pointer. - fn validate_param(&mut self, world: &World) -> Result<(), SystemParamValidationError> { - let world_cell = world.as_unsafe_world_cell_readonly(); - // SAFETY: - // - We have exclusive access to the entire world. - unsafe { self.validate_param_unsafe(world_cell) } - } - - /// Initialize the system. - /// - /// Returns a [`FilteredAccessSet`] with the access required to run the system. - fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet; - - /// Checks any [`Tick`]s stored on this system and wraps their value if they get too old. - /// - /// This method must be called periodically to ensure that change detection behaves correctly. - /// When using bevy's default configuration, this will be called for you as needed. - fn check_change_tick(&mut self, check: CheckChangeTicks); - - /// Returns the system's default [system sets](crate::schedule::SystemSet). - /// - /// Each system will create a default system set that contains the system. - fn default_system_sets(&self) -> Vec { - Vec::new() - } - - /// Gets the tick indicating the last time this system ran. - fn get_last_run(&self) -> Tick; - - /// Overwrites the tick indicating the last time this system ran. - /// - /// # Warning - /// This is a complex and error-prone operation, that can have unexpected consequences on any system relying on this code. - /// However, it can be an essential escape hatch when, for example, - /// you are trying to synchronize representations using change detection and need to avoid infinite recursion. - fn set_last_run(&mut self, last_run: Tick); -} - -/// [`System`] types that do not modify the [`World`] when run. -/// This is implemented for any systems whose parameters all implement [`ReadOnlySystemParam`]. -/// -/// Note that systems which perform [deferred](System::apply_deferred) mutations (such as with [`Commands`]) -/// may implement this trait. -/// -/// [`ReadOnlySystemParam`]: crate::system::ReadOnlySystemParam -/// [`Commands`]: crate::system::Commands -/// -/// # Safety -/// -/// This must only be implemented for system types which do not mutate the `World` -/// when [`System::run_unsafe`] is called. -pub unsafe trait ReadOnlySystem: System { - /// Runs this system with the given input in the world. - /// - /// Unlike [`System::run`], this can be called with a shared reference to the world, - /// since this system is known not to modify the world. - fn run_readonly( - &mut self, - input: SystemIn<'_, Self>, - world: &World, - ) -> Result { - let world = world.as_unsafe_world_cell_readonly(); - // SAFETY: - // - We have read-only access to the entire world. - unsafe { self.validate_param_unsafe(world) }?; - // SAFETY: - // - We have read-only access to the entire world. - // - `update_archetype_component_access` has been called. - unsafe { self.run_unsafe(input, world) } - } -} - -/// A convenience type alias for a boxed [`System`] trait object. -pub type BoxedSystem = Box>; - -pub(crate) fn check_system_change_tick( - last_run: &mut Tick, - check: CheckChangeTicks, - system_name: DebugName, -) { - if last_run.check_tick(check) { - let age = check.present_tick().relative_to(*last_run).get(); - warn!( - "System '{system_name}' has not run for {age} ticks. \ - Changes older than {} ticks will not be detected.", - Tick::MAX.get() - 1, - ); - } -} - -impl Debug for dyn System -where - In: SystemInput + 'static, - Out: 'static, -{ - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("System") - .field("name", &self.name()) - .field("is_exclusive", &self.is_exclusive()) - .field("is_send", &self.is_send()) - .finish_non_exhaustive() - } -} - -/// Trait used to run a system immediately on a [`World`]. -/// -/// # Warning -/// This function is not an efficient method of running systems and it's meant to be used as a utility -/// for testing and/or diagnostics. -/// -/// Systems called through [`run_system_once`](RunSystemOnce::run_system_once) do not hold onto any state, -/// as they are created and destroyed every time [`run_system_once`](RunSystemOnce::run_system_once) is called. -/// Practically, this means that [`Local`](crate::system::Local) variables are -/// reset on every run and change detection does not work. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::RunSystemOnce; -/// #[derive(Resource, Default)] -/// struct Counter(u8); -/// -/// fn increment(mut counter: Local) { -/// counter.0 += 1; -/// println!("{}", counter.0); -/// } -/// -/// let mut world = World::default(); -/// world.run_system_once(increment); // prints 1 -/// world.run_system_once(increment); // still prints 1 -/// ``` -/// -/// If you do need systems to hold onto state between runs, use [`World::run_system_cached`](World::run_system_cached) -/// or [`World::run_system`](World::run_system). -/// -/// # Usage -/// Typically, to test a system, or to extract specific diagnostics information from a world, -/// you'd need a [`Schedule`](crate::schedule::Schedule) to run the system. This can create redundant boilerplate code -/// when writing tests or trying to quickly iterate on debug specific systems. -/// -/// For these situations, this function can be useful because it allows you to execute a system -/// immediately with some custom input and retrieve its output without requiring the necessary boilerplate. -/// -/// # Examples -/// -/// ## Immediate Command Execution -/// -/// This usage is helpful when trying to test systems or functions that operate on [`Commands`](crate::system::Commands): -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::RunSystemOnce; -/// let mut world = World::default(); -/// let entity = world.run_system_once(|mut commands: Commands| { -/// commands.spawn_empty().id() -/// }).unwrap(); -/// # assert!(world.get_entity(entity).is_ok()); -/// ``` -/// -/// ## Immediate Queries -/// -/// This usage is helpful when trying to run an arbitrary query on a world for testing or debugging purposes: -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::RunSystemOnce; -/// -/// #[derive(Component)] -/// struct T(usize); -/// -/// let mut world = World::default(); -/// world.spawn(T(0)); -/// world.spawn(T(1)); -/// world.spawn(T(1)); -/// let count = world.run_system_once(|query: Query<&T>| { -/// query.iter().filter(|t| t.0 == 1).count() -/// }).unwrap(); -/// -/// # assert_eq!(count, 2); -/// ``` -/// -/// Note that instead of closures you can also pass in regular functions as systems: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::RunSystemOnce; -/// -/// #[derive(Component)] -/// struct T(usize); -/// -/// fn count(query: Query<&T>) -> usize { -/// query.iter().filter(|t| t.0 == 1).count() -/// } -/// -/// let mut world = World::default(); -/// world.spawn(T(0)); -/// world.spawn(T(1)); -/// world.spawn(T(1)); -/// let count = world.run_system_once(count).unwrap(); -/// -/// # assert_eq!(count, 2); -/// ``` -pub trait RunSystemOnce: Sized { - /// Tries to run a system and apply its deferred parameters. - fn run_system_once(self, system: T) -> Result - where - T: IntoSystem<(), Out, Marker>, - { - self.run_system_once_with(system, ()) - } - - /// Tries to run a system with given input and apply deferred parameters. - fn run_system_once_with( - self, - system: T, - input: SystemIn<'_, T::System>, - ) -> Result - where - T: IntoSystem, - In: SystemInput; -} - -impl RunSystemOnce for &mut World { - fn run_system_once_with( - self, - system: T, - input: SystemIn<'_, T::System>, - ) -> Result - where - T: IntoSystem, - In: SystemInput, - { - let mut system: T::System = IntoSystem::into_system(system); - system.initialize(self); - system.run(input, self) - } -} - -/// Running system failed. -#[derive(Debug)] -pub enum RunSystemError { - /// System could not be run due to parameters that failed validation. - /// This is not considered an error. - Skipped(SystemParamValidationError), - /// System returned an error or failed required parameter validation. - Failed(BevyError), -} - -impl Display for RunSystemError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Skipped(err) => write!( - f, - "System did not run due to failed parameter validation: {err}" - ), - Self::Failed(err) => write!(f, "{err}"), - } - } -} - -impl From for RunSystemError -where - BevyError: From, -{ - fn from(mut value: E) -> Self { - // Specialize the impl so that a skipped `SystemParamValidationError` - // is converted to `Skipped` instead of `Failed`. - // Note that the `downcast_mut` check is based on the static type, - // and can be optimized out after monomorphization. - let any: &mut dyn Any = &mut value; - if let Some(err) = any.downcast_mut::() { - if err.skipped { - return Self::Skipped(core::mem::replace(err, SystemParamValidationError::EMPTY)); - } - } - Self::Failed(From::from(value)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::prelude::*; - use alloc::string::ToString; - - #[test] - fn run_system_once() { - struct T(usize); - - impl Resource for T {} - - fn system(In(n): In, mut commands: Commands) -> usize { - commands.insert_resource(T(n)); - n + 1 - } - - let mut world = World::default(); - let n = world.run_system_once_with(system, 1).unwrap(); - assert_eq!(n, 2); - assert_eq!(world.resource::().0, 1); - } - - #[derive(Resource, Default, PartialEq, Debug)] - struct Counter(u8); - - fn count_up(mut counter: ResMut) { - counter.0 += 1; - } - - #[test] - fn run_two_systems() { - let mut world = World::new(); - world.init_resource::(); - assert_eq!(*world.resource::(), Counter(0)); - world.run_system_once(count_up).unwrap(); - assert_eq!(*world.resource::(), Counter(1)); - world.run_system_once(count_up).unwrap(); - assert_eq!(*world.resource::(), Counter(2)); - } - - fn spawn_entity(mut commands: Commands) { - commands.spawn_empty(); - } - - #[test] - fn command_processing() { - let mut world = World::new(); - assert_eq!(world.entities.len(), 0); - world.run_system_once(spawn_entity).unwrap(); - assert_eq!(world.entities.len(), 1); - } - - #[test] - fn non_send_resources() { - fn non_send_count_down(mut ns: NonSendMut) { - ns.0 -= 1; - } - - let mut world = World::new(); - world.insert_non_send_resource(Counter(10)); - assert_eq!(*world.non_send_resource::(), Counter(10)); - world.run_system_once(non_send_count_down).unwrap(); - assert_eq!(*world.non_send_resource::(), Counter(9)); - } - - #[test] - fn run_system_once_invalid_params() { - struct T; - impl Resource for T {} - fn system(_: Res) {} - - let mut world = World::default(); - // This fails because `T` has not been added to the world yet. - let result = world.run_system_once(system); - - assert!(matches!(result, Err(RunSystemError::Failed { .. }))); - let expected = "Parameter `Res` failed validation: Resource does not exist\n"; - assert!(result.unwrap_err().to_string().contains(expected)); - } -} diff --git a/src/system/system_name.rs b/src/system/system_name.rs deleted file mode 100644 index e0c3c95..0000000 --- a/src/system/system_name.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::{ - component::{ComponentId, Tick}, - prelude::World, - query::FilteredAccessSet, - system::{ExclusiveSystemParam, ReadOnlySystemParam, SystemMeta, SystemParam}, - world::unsafe_world_cell::UnsafeWorldCell, -}; -use bevy_utils::prelude::DebugName; -use derive_more::derive::{Display, Into}; - -/// [`SystemParam`] that returns the name of the system which it is used in. -/// -/// This is not a reliable identifier, it is more so useful for debugging or logging. -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::system::SystemName; -/// # use bevy_ecs::system::SystemParam; -/// -/// #[derive(SystemParam)] -/// struct Logger { -/// system_name: SystemName, -/// } -/// -/// impl Logger { -/// fn log(&mut self, message: &str) { -/// eprintln!("{}: {}", self.system_name, message); -/// } -/// } -/// -/// fn system1(mut logger: Logger) { -/// // Prints: "crate_name::mod_name::system1: Hello". -/// logger.log("Hello"); -/// } -/// ``` -#[derive(Debug, Into, Display)] -pub struct SystemName(DebugName); - -impl SystemName { - /// Gets the name of the system. - pub fn name(&self) -> DebugName { - self.0.clone() - } -} - -// SAFETY: no component value access -unsafe impl SystemParam for SystemName { - type State = (); - type Item<'w, 's> = SystemName; - - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - } - - #[inline] - unsafe fn get_param<'w, 's>( - _state: &'s mut Self::State, - system_meta: &SystemMeta, - _world: UnsafeWorldCell<'w>, - _change_tick: Tick, - ) -> Self::Item<'w, 's> { - SystemName(system_meta.name.clone()) - } -} - -// SAFETY: Only reads internal system state -unsafe impl ReadOnlySystemParam for SystemName {} - -impl ExclusiveSystemParam for SystemName { - type State = (); - type Item<'s> = SystemName; - - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {} - - fn get_param<'s>(_state: &'s mut Self::State, system_meta: &SystemMeta) -> Self::Item<'s> { - SystemName(system_meta.name.clone()) - } -} - -#[cfg(test)] -#[cfg(feature = "trace")] -mod tests { - use crate::{ - system::{IntoSystem, RunSystemOnce, SystemName}, - world::World, - }; - use alloc::{borrow::ToOwned, string::String}; - - #[test] - fn test_system_name_regular_param() { - fn testing(name: SystemName) -> String { - name.name().as_string() - } - - let mut world = World::default(); - let id = world.register_system(testing); - let name = world.run_system(id).unwrap(); - assert!(name.ends_with("testing")); - } - - #[test] - fn test_system_name_exclusive_param() { - fn testing(_world: &mut World, name: SystemName) -> String { - name.name().as_string() - } - - let mut world = World::default(); - let id = world.register_system(testing); - let name = world.run_system(id).unwrap(); - assert!(name.ends_with("testing")); - } - - #[test] - fn test_closure_system_name_regular_param() { - let mut world = World::default(); - let system = - IntoSystem::into_system(|name: SystemName| name.name().to_owned()).with_name("testing"); - let name = world.run_system_once(system).unwrap().as_string(); - assert_eq!(name, "testing"); - } - - #[test] - fn test_exclusive_closure_system_name_regular_param() { - let mut world = World::default(); - let system = - IntoSystem::into_system(|_world: &mut World, name: SystemName| name.name().to_owned()) - .with_name("testing"); - let name = world.run_system_once(system).unwrap().as_string(); - assert_eq!(name, "testing"); - } -} diff --git a/src/system/system_param.rs b/src/system/system_param.rs deleted file mode 100644 index d552bf1..0000000 --- a/src/system/system_param.rs +++ /dev/null @@ -1,3116 +0,0 @@ -pub use crate::change_detection::{NonSendMut, Res, ResMut}; -use crate::{ - archetype::Archetypes, - bundle::Bundles, - change_detection::{MaybeLocation, Ticks, TicksMut}, - component::{ComponentId, ComponentTicks, Components, Tick}, - entity::Entities, - query::{ - Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError, - QueryState, ReadOnlyQueryData, - }, - resource::Resource, - storage::ResourceData, - system::{Query, Single, SystemMeta}, - world::{ - unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, FilteredResourcesMut, - FromWorld, World, - }, -}; -use alloc::{ - borrow::{Cow, ToOwned}, - boxed::Box, - vec::Vec, -}; -pub use bevy_ecs_macros::SystemParam; -use bevy_platform::cell::SyncCell; -use bevy_ptr::UnsafeCellDeref; -use bevy_utils::prelude::DebugName; -use core::{ - any::Any, - fmt::{Debug, Display}, - marker::PhantomData, - ops::{Deref, DerefMut}, - panic::Location, -}; -use thiserror::Error; - -use super::Populated; -use variadics_please::{all_tuples, all_tuples_enumerated}; - -/// A parameter that can be used in a [`System`](super::System). -/// -/// # Derive -/// -/// This trait can be derived with the [`derive@super::SystemParam`] macro. -/// This macro only works if each field on the derived struct implements [`SystemParam`]. -/// Note: There are additional requirements on the field types. -/// See the *Generic `SystemParam`s* section for details and workarounds of the probable -/// cause if this derive causes an error to be emitted. -/// -/// Derived `SystemParam` structs may have two lifetimes: `'w` for data stored in the [`World`], -/// and `'s` for data stored in the parameter's state. -/// -/// The following list shows the most common [`SystemParam`]s and which lifetime they require -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # #[derive(Resource)] -/// # struct SomeResource; -/// # #[derive(Event, BufferedEvent)] -/// # struct SomeEvent; -/// # #[derive(Resource)] -/// # struct SomeOtherResource; -/// # use bevy_ecs::system::SystemParam; -/// # #[derive(SystemParam)] -/// # struct ParamsExample<'w, 's> { -/// # query: -/// Query<'w, 's, Entity>, -/// # res: -/// Res<'w, SomeResource>, -/// # res_mut: -/// ResMut<'w, SomeOtherResource>, -/// # local: -/// Local<'s, u8>, -/// # commands: -/// Commands<'w, 's>, -/// # eventreader: -/// EventReader<'w, 's, SomeEvent>, -/// # eventwriter: -/// EventWriter<'w, SomeEvent> -/// # } -/// ``` -/// ## `PhantomData` -/// -/// [`PhantomData`] is a special type of `SystemParam` that does nothing. -/// This is useful for constraining generic types or lifetimes. -/// -/// # Example -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # #[derive(Resource)] -/// # struct SomeResource; -/// use std::marker::PhantomData; -/// use bevy_ecs::system::SystemParam; -/// -/// #[derive(SystemParam)] -/// struct MyParam<'w, Marker: 'static> { -/// foo: Res<'w, SomeResource>, -/// marker: PhantomData, -/// } -/// -/// fn my_system(param: MyParam) { -/// // Access the resource through `param.foo` -/// } -/// -/// # bevy_ecs::system::assert_is_system(my_system::<()>); -/// ``` -/// -/// # Generic `SystemParam`s -/// -/// When using the derive macro, you may see an error in the form of: -/// -/// ```text -/// expected ... [ParamType] -/// found associated type `<[ParamType] as SystemParam>::Item<'_, '_>` -/// ``` -/// where `[ParamType]` is the type of one of your fields. -/// To solve this error, you can wrap the field of type `[ParamType]` with [`StaticSystemParam`] -/// (i.e. `StaticSystemParam<[ParamType]>`). -/// -/// ## Details -/// -/// The derive macro requires that the [`SystemParam`] implementation of -/// each field `F`'s [`Item`](`SystemParam::Item`)'s is itself `F` -/// (ignoring lifetimes for simplicity). -/// This assumption is due to type inference reasons, so that the derived [`SystemParam`] can be -/// used as an argument to a function system. -/// If the compiler cannot validate this property for `[ParamType]`, it will error in the form shown above. -/// -/// This will most commonly occur when working with `SystemParam`s generically, as the requirement -/// has not been proven to the compiler. -/// -/// ## Custom Validation Messages -/// -/// When using the derive macro, any [`SystemParamValidationError`]s will be propagated from the sub-parameters. -/// If you want to override the error message, add a `#[system_param(validation_message = "New message")]` attribute to the parameter. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # #[derive(Resource)] -/// # struct SomeResource; -/// # use bevy_ecs::system::SystemParam; -/// # -/// #[derive(SystemParam)] -/// struct MyParam<'w> { -/// #[system_param(validation_message = "Custom Message")] -/// foo: Res<'w, SomeResource>, -/// } -/// -/// let mut world = World::new(); -/// let err = world.run_system_cached(|param: MyParam| {}).unwrap_err(); -/// let expected = "Parameter `MyParam::foo` failed validation: Custom Message"; -/// # #[cfg(feature="Trace")] // Without debug_utils/debug enabled MyParam::foo is stripped and breaks the assert -/// assert!(err.to_string().contains(expected)); -/// ``` -/// -/// ## Builders -/// -/// If you want to use a [`SystemParamBuilder`](crate::system::SystemParamBuilder) with a derived [`SystemParam`] implementation, -/// add a `#[system_param(builder)]` attribute to the struct. -/// This will generate a builder struct whose name is the param struct suffixed with `Builder`. -/// The builder will not be `pub`, so you may want to expose a method that returns an `impl SystemParamBuilder`. -/// -/// ``` -/// mod custom_param { -/// # use bevy_ecs::{ -/// # prelude::*, -/// # system::{LocalBuilder, QueryParamBuilder, SystemParam}, -/// # }; -/// # -/// #[derive(SystemParam)] -/// #[system_param(builder)] -/// pub struct CustomParam<'w, 's> { -/// query: Query<'w, 's, ()>, -/// local: Local<'s, usize>, -/// } -/// -/// impl<'w, 's> CustomParam<'w, 's> { -/// pub fn builder( -/// local: usize, -/// query: impl FnOnce(&mut QueryBuilder<()>), -/// ) -> impl SystemParamBuilder { -/// CustomParamBuilder { -/// local: LocalBuilder(local), -/// query: QueryParamBuilder::new(query), -/// } -/// } -/// } -/// } -/// -/// use custom_param::CustomParam; -/// -/// # use bevy_ecs::prelude::*; -/// # #[derive(Component)] -/// # struct A; -/// # -/// # let mut world = World::new(); -/// # -/// let system = (CustomParam::builder(100, |builder| { -/// builder.with::
(); -/// }),) -/// .build_state(&mut world) -/// .build_system(|param: CustomParam| {}); -/// ``` -/// -/// # Safety -/// -/// The implementor must ensure the following is true. -/// - [`SystemParam::init_access`] correctly registers all [`World`] accesses used -/// by [`SystemParam::get_param`] with the provided [`system_meta`](SystemMeta). -/// - None of the world accesses may conflict with any prior accesses registered -/// on `system_meta`. -pub unsafe trait SystemParam: Sized { - /// Used to store data which persists across invocations of a system. - type State: Send + Sync + 'static; - - /// The item type returned when constructing this system param. - /// The value of this associated type should be `Self`, instantiated with new lifetimes. - /// - /// You could think of [`SystemParam::Item<'w, 's>`] as being an *operation* that changes the lifetimes bound to `Self`. - type Item<'world, 'state>: SystemParam; - - /// Creates a new instance of this param's [`State`](SystemParam::State). - fn init_state(world: &mut World) -> Self::State; - - /// Registers any [`World`] access used by this [`SystemParam`] - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ); - - /// Applies any deferred mutations stored in this [`SystemParam`]'s state. - /// This is used to apply [`Commands`] during [`ApplyDeferred`](crate::prelude::ApplyDeferred). - /// - /// [`Commands`]: crate::prelude::Commands - #[inline] - #[expect( - unused_variables, - reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." - )] - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {} - - /// Queues any deferred mutations to be applied at the next [`ApplyDeferred`](crate::prelude::ApplyDeferred). - #[inline] - #[expect( - unused_variables, - reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." - )] - fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {} - - /// Validates that the param can be acquired by the [`get_param`](SystemParam::get_param). - /// - /// Built-in executors use this to prevent systems with invalid params from running, - /// and any failures here will be bubbled up to the default error handler defined in [`bevy_ecs::error`], - /// with a value of type [`SystemParamValidationError`]. - /// - /// For nested [`SystemParam`]s validation will fail if any - /// delegated validation fails. - /// - /// However calling and respecting [`SystemParam::validate_param`] - /// is not a strict requirement, [`SystemParam::get_param`] should - /// provide it's own safety mechanism to prevent undefined behavior. - /// - /// The [`world`](UnsafeWorldCell) can only be used to read param's data - /// and world metadata. No data can be written. - /// - /// When using system parameters that require `change_tick` you can use - /// [`UnsafeWorldCell::change_tick()`]. Even if this isn't the exact - /// same tick used for [`SystemParam::get_param`], the world access - /// ensures that the queried data will be the same in both calls. - /// - /// This method has to be called directly before [`SystemParam::get_param`] with no other (relevant) - /// world mutations inbetween. Otherwise, while it won't lead to any undefined behavior, - /// the validity of the param may change. - /// - /// [`System::validate_param`](super::system::System::validate_param), - /// calls this method for each supplied system param. - /// - /// # Safety - /// - /// - The passed [`UnsafeWorldCell`] must have read-only access to world data - /// registered in [`init_access`](SystemParam::init_access). - /// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state). - #[expect( - unused_variables, - reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." - )] - unsafe fn validate_param( - state: &mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - Ok(()) - } - - /// Creates a parameter to be passed into a [`SystemParamFunction`](super::SystemParamFunction). - /// - /// # Safety - /// - /// - The passed [`UnsafeWorldCell`] must have access to any world data registered - /// in [`init_access`](SystemParam::init_access). - /// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state). - unsafe fn get_param<'world, 'state>( - state: &'state mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - change_tick: Tick, - ) -> Self::Item<'world, 'state>; -} - -/// A [`SystemParam`] that only reads a given [`World`]. -/// -/// # Safety -/// This must only be implemented for [`SystemParam`] impls that exclusively read the World passed in to [`SystemParam::get_param`] -pub unsafe trait ReadOnlySystemParam: SystemParam {} - -/// Shorthand way of accessing the associated type [`SystemParam::Item`] for a given [`SystemParam`]. -pub type SystemParamItem<'w, 's, P> =

::Item<'w, 's>; - -// SAFETY: QueryState is constrained to read-only fetches, so it only reads World. -unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam - for Query<'w, 's, D, F> -{ -} - -// SAFETY: Relevant query ComponentId access is applied to SystemMeta. If -// this Query conflicts with any prior access, a panic will occur. -unsafe impl SystemParam for Query<'_, '_, D, F> { - type State = QueryState; - type Item<'w, 's> = Query<'w, 's, D, F>; - - fn init_state(world: &mut World) -> Self::State { - QueryState::new(world) - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - assert_component_access_compatibility( - &system_meta.name, - DebugName::type_name::(), - DebugName::type_name::(), - component_access_set, - &state.component_access, - world, - ); - component_access_set.add(state.component_access.clone()); - } - - #[inline] - unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - // SAFETY: We have registered all of the query's world accesses, - // so the caller ensures that `world` has permission to access any - // world data that the query needs. - // The caller ensures the world matches the one used in init_state. - unsafe { state.query_unchecked_with_ticks(world, system_meta.last_run, change_tick) } - } -} - -fn assert_component_access_compatibility( - system_name: &DebugName, - query_type: DebugName, - filter_type: DebugName, - system_access: &FilteredAccessSet, - current: &FilteredAccess, - world: &World, -) { - let conflicts = system_access.get_conflicts_single(current); - if conflicts.is_empty() { - return; - } - let mut accesses = conflicts.format_conflict_list(world); - // Access list may be empty (if access to all components requested) - if !accesses.is_empty() { - accesses.push(' '); - } - panic!("error[B0001]: Query<{}, {}> in system {system_name} accesses component(s) {accesses}in a way that conflicts with a previous system parameter. Consider using `Without` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001", query_type.shortname(), filter_type.shortname()); -} - -// SAFETY: Relevant query ComponentId access is applied to SystemMeta. If -// this Query conflicts with any prior access, a panic will occur. -unsafe impl<'a, 'b, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam - for Single<'a, 'b, D, F> -{ - type State = QueryState; - type Item<'w, 's> = Single<'w, 's, D, F>; - - fn init_state(world: &mut World) -> Self::State { - Query::init_state(world) - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - Query::init_access(state, system_meta, component_access_set, world); - } - - #[inline] - unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - // SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere. - // The caller ensures the world matches the one used in init_state. - let query = - unsafe { state.query_unchecked_with_ticks(world, system_meta.last_run, change_tick) }; - let single = query - .single_inner() - .expect("The query was expected to contain exactly one matching entity."); - Single { - item: single, - _filter: PhantomData, - } - } - - #[inline] - unsafe fn validate_param( - state: &mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere - // and the query is read only. - // The caller ensures the world matches the one used in init_state. - let query = unsafe { - state.query_unchecked_with_ticks(world, system_meta.last_run, world.change_tick()) - }; - match query.single_inner() { - Ok(_) => Ok(()), - Err(QuerySingleError::NoEntities(_)) => Err( - SystemParamValidationError::skipped::("No matching entities"), - ), - Err(QuerySingleError::MultipleEntities(_)) => Err( - SystemParamValidationError::skipped::("Multiple matching entities"), - ), - } - } -} - -// SAFETY: QueryState is constrained to read-only fetches, so it only reads World. -unsafe impl<'a, 'b, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam - for Single<'a, 'b, D, F> -{ -} - -// SAFETY: Relevant query ComponentId access is applied to SystemMeta. If -// this Query conflicts with any prior access, a panic will occur. -unsafe impl SystemParam - for Populated<'_, '_, D, F> -{ - type State = QueryState; - type Item<'w, 's> = Populated<'w, 's, D, F>; - - fn init_state(world: &mut World) -> Self::State { - Query::init_state(world) - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - Query::init_access(state, system_meta, component_access_set, world); - } - - #[inline] - unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - // SAFETY: Delegate to existing `SystemParam` implementations. - let query = unsafe { Query::get_param(state, system_meta, world, change_tick) }; - Populated(query) - } - - #[inline] - unsafe fn validate_param( - state: &mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // SAFETY: - // - We have read-only access to the components accessed by query. - // - The caller ensures the world matches the one used in init_state. - let query = unsafe { - state.query_unchecked_with_ticks(world, system_meta.last_run, world.change_tick()) - }; - if query.is_empty() { - Err(SystemParamValidationError::skipped::( - "No matching entities", - )) - } else { - Ok(()) - } - } -} - -// SAFETY: QueryState is constrained to read-only fetches, so it only reads World. -unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam - for Populated<'w, 's, D, F> -{ -} - -/// A collection of potentially conflicting [`SystemParam`]s allowed by disjoint access. -/// -/// Allows systems to safely access and interact with up to 8 mutually exclusive [`SystemParam`]s, such as -/// two queries that reference the same mutable data or an event reader and writer of the same type. -/// -/// Each individual [`SystemParam`] can be accessed by using the functions `p0()`, `p1()`, ..., `p7()`, -/// according to the order they are defined in the `ParamSet`. This ensures that there's either -/// only one mutable reference to a parameter at a time or any number of immutable references. -/// -/// # Examples -/// -/// The following system mutably accesses the same component two times, -/// which is not allowed due to rust's mutability rules. -/// -/// ```should_panic -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct Health; -/// # -/// # #[derive(Component)] -/// # struct Enemy; -/// # -/// # #[derive(Component)] -/// # struct Ally; -/// # -/// // This will panic at runtime when the system gets initialized. -/// fn bad_system( -/// mut enemies: Query<&mut Health, With>, -/// mut allies: Query<&mut Health, With>, -/// ) { -/// // ... -/// } -/// # -/// # let mut bad_system_system = IntoSystem::into_system(bad_system); -/// # let mut world = World::new(); -/// # bad_system_system.initialize(&mut world); -/// # bad_system_system.run((), &mut world); -/// ``` -/// -/// Conflicting `SystemParam`s like these can be placed in a `ParamSet`, -/// which leverages the borrow checker to ensure that only one of the contained parameters are accessed at a given time. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct Health; -/// # -/// # #[derive(Component)] -/// # struct Enemy; -/// # -/// # #[derive(Component)] -/// # struct Ally; -/// # -/// // Given the following system -/// fn fancy_system( -/// mut set: ParamSet<( -/// Query<&mut Health, With>, -/// Query<&mut Health, With>, -/// )> -/// ) { -/// // This will access the first `SystemParam`. -/// for mut health in set.p0().iter_mut() { -/// // Do your fancy stuff here... -/// } -/// -/// // The second `SystemParam`. -/// // This would fail to compile if the previous parameter was still borrowed. -/// for mut health in set.p1().iter_mut() { -/// // Do even fancier stuff here... -/// } -/// } -/// # bevy_ecs::system::assert_is_system(fancy_system); -/// ``` -/// -/// Of course, `ParamSet`s can be used with any kind of `SystemParam`, not just [queries](Query). -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Event, BufferedEvent)] -/// # struct MyEvent; -/// # impl MyEvent { -/// # pub fn new() -> Self { Self } -/// # } -/// fn event_system( -/// mut set: ParamSet<( -/// // PROBLEM: `EventReader` and `EventWriter` cannot be used together normally, -/// // because they both need access to the same event queue. -/// // SOLUTION: `ParamSet` allows these conflicting parameters to be used safely -/// // by ensuring only one is accessed at a time. -/// EventReader, -/// EventWriter, -/// // PROBLEM: `&World` needs read access to everything, which conflicts with -/// // any mutable access in the same system. -/// // SOLUTION: `ParamSet` ensures `&World` is only accessed when we're not -/// // using the other mutable parameters. -/// &World, -/// )>, -/// ) { -/// for event in set.p0().read() { -/// // ... -/// # let _event = event; -/// } -/// set.p1().write(MyEvent::new()); -/// -/// let entities = set.p2().entities(); -/// // ... -/// # let _entities = entities; -/// } -/// # bevy_ecs::system::assert_is_system(event_system); -/// ``` -pub struct ParamSet<'w, 's, T: SystemParam> { - param_states: &'s mut T::State, - world: UnsafeWorldCell<'w>, - system_meta: SystemMeta, - change_tick: Tick, -} - -macro_rules! impl_param_set { - ($(($index: tt, $param: ident, $fn_name: ident)),*) => { - // SAFETY: All parameters are constrained to ReadOnlySystemParam, so World is only read - unsafe impl<'w, 's, $($param,)*> ReadOnlySystemParam for ParamSet<'w, 's, ($($param,)*)> - where $($param: ReadOnlySystemParam,)* - { } - - // SAFETY: Relevant parameter ComponentId access is applied to SystemMeta. If any ParamState conflicts - // with any prior access, a panic will occur. - unsafe impl<'_w, '_s, $($param: SystemParam,)*> SystemParam for ParamSet<'_w, '_s, ($($param,)*)> - { - type State = ($($param::State,)*); - type Item<'w, 's> = ParamSet<'w, 's, ($($param,)*)>; - - #[expect( - clippy::allow_attributes, - reason = "This is inside a macro meant for tuples; as such, `non_snake_case` won't always lint." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - fn init_state(world: &mut World) -> Self::State { - ($($param::init_state(world),)*) - } - - #[expect( - clippy::allow_attributes, - reason = "This is inside a macro meant for tuples; as such, `non_snake_case` won't always lint." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - fn init_access(state: &Self::State, system_meta: &mut SystemMeta, component_access_set: &mut FilteredAccessSet, world: &mut World) { - let ($($param,)*) = state; - $( - // Call `init_access` on a clone of the original access set to check for conflicts - let component_access_set_clone = &mut component_access_set.clone(); - $param::init_access($param, system_meta, component_access_set_clone, world); - )* - $( - // Pretend to add the param to the system alone to gather the new access, - // then merge its access into the system. - let mut access_set = FilteredAccessSet::new(); - $param::init_access($param, system_meta, &mut access_set, world); - component_access_set.extend(access_set); - )* - } - - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { - <($($param,)*) as SystemParam>::apply(state, system_meta, world); - } - - fn queue(state: &mut Self::State, system_meta: &SystemMeta, mut world: DeferredWorld) { - <($($param,)*) as SystemParam>::queue(state, system_meta, world.reborrow()); - } - - #[inline] - unsafe fn validate_param<'w, 's>( - state: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - ) -> Result<(), SystemParamValidationError> { - <($($param,)*) as SystemParam>::validate_param(state, system_meta, world) - } - - #[inline] - unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - ParamSet { - param_states: state, - system_meta: system_meta.clone(), - world, - change_tick, - } - } - } - - impl<'w, 's, $($param: SystemParam,)*> ParamSet<'w, 's, ($($param,)*)> - { - $( - /// Gets exclusive access to the parameter at index - #[doc = stringify!($index)] - /// in this [`ParamSet`]. - /// No other parameters may be accessed while this one is active. - pub fn $fn_name<'a>(&'a mut self) -> SystemParamItem<'a, 'a, $param> { - // SAFETY: systems run without conflicts with other systems. - // Conflicting params in ParamSet are not accessible at the same time - // ParamSets are guaranteed to not conflict with other SystemParams - unsafe { - $param::get_param(&mut self.param_states.$index, &self.system_meta, self.world, self.change_tick) - } - } - )* - } - } -} - -all_tuples_enumerated!(impl_param_set, 1, 8, P, p); - -// SAFETY: Res only reads a single World resource -unsafe impl<'a, T: Resource> ReadOnlySystemParam for Res<'a, T> {} - -// SAFETY: Res ComponentId access is applied to SystemMeta. If this Res -// conflicts with any prior access, a panic will occur. -unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { - type State = ComponentId; - type Item<'w, 's> = Res<'w, T>; - - fn init_state(world: &mut World) -> Self::State { - world.components_registrator().register_resource::() - } - - fn init_access( - &component_id: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - let combined_access = component_access_set.combined_access(); - assert!( - !combined_access.has_resource_write(component_id), - "error[B0002]: Res<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", - DebugName::type_name::(), - system_meta.name, - ); - - component_access_set.add_unfiltered_resource_read(component_id); - } - - #[inline] - unsafe fn validate_param( - &mut component_id: &mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // SAFETY: Read-only access to resource metadata. - if unsafe { world.storages() } - .resources - .get(component_id) - .is_some_and(ResourceData::is_present) - { - Ok(()) - } else { - Err(SystemParamValidationError::invalid::( - "Resource does not exist", - )) - } - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - let (ptr, ticks, caller) = - world - .get_resource_with_ticks(component_id) - .unwrap_or_else(|| { - panic!( - "Resource requested by {} does not exist: {}", - system_meta.name, - DebugName::type_name::() - ); - }); - Res { - value: ptr.deref(), - ticks: Ticks { - added: ticks.added.deref(), - changed: ticks.changed.deref(), - last_run: system_meta.last_run, - this_run: change_tick, - }, - changed_by: caller.map(|caller| caller.deref()), - } - } -} - -// SAFETY: Res ComponentId access is applied to SystemMeta. If this Res -// conflicts with any prior access, a panic will occur. -unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { - type State = ComponentId; - type Item<'w, 's> = ResMut<'w, T>; - - fn init_state(world: &mut World) -> Self::State { - world.components_registrator().register_resource::() - } - - fn init_access( - &component_id: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - let combined_access = component_access_set.combined_access(); - if combined_access.has_resource_write(component_id) { - panic!( - "error[B0002]: ResMut<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", - DebugName::type_name::(), system_meta.name); - } else if combined_access.has_resource_read(component_id) { - panic!( - "error[B0002]: ResMut<{}> in system {} conflicts with a previous Res<{0}> access. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", - DebugName::type_name::(), system_meta.name); - } - component_access_set.add_unfiltered_resource_write(component_id); - } - - #[inline] - unsafe fn validate_param( - &mut component_id: &mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // SAFETY: Read-only access to resource metadata. - if unsafe { world.storages() } - .resources - .get(component_id) - .is_some_and(ResourceData::is_present) - { - Ok(()) - } else { - Err(SystemParamValidationError::invalid::( - "Resource does not exist", - )) - } - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - let value = world - .get_resource_mut_by_id(component_id) - .unwrap_or_else(|| { - panic!( - "Resource requested by {} does not exist: {}", - system_meta.name, - DebugName::type_name::() - ); - }); - ResMut { - value: value.value.deref_mut::(), - ticks: TicksMut { - added: value.ticks.added, - changed: value.ticks.changed, - last_run: system_meta.last_run, - this_run: change_tick, - }, - changed_by: value.changed_by, - } - } -} - -/// SAFETY: only reads world -unsafe impl<'w> ReadOnlySystemParam for &'w World {} - -// SAFETY: `read_all` access is set and conflicts result in a panic -unsafe impl SystemParam for &'_ World { - type State = (); - type Item<'w, 's> = &'w World; - - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - _system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - let mut filtered_access = FilteredAccess::default(); - - filtered_access.read_all(); - if !component_access_set - .get_conflicts_single(&filtered_access) - .is_empty() - { - panic!("&World conflicts with a previous mutable system parameter. Allowing this would break Rust's mutability rules"); - } - component_access_set.add(filtered_access); - } - - #[inline] - unsafe fn get_param<'w, 's>( - _state: &'s mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - _change_tick: Tick, - ) -> Self::Item<'w, 's> { - // SAFETY: Read-only access to the entire world was registered in `init_state`. - unsafe { world.world() } - } -} - -/// SAFETY: `DeferredWorld` can read all components and resources but cannot be used to gain any other mutable references. -unsafe impl<'w> SystemParam for DeferredWorld<'w> { - type State = (); - type Item<'world, 'state> = DeferredWorld<'world>; - - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - assert!( - !component_access_set.combined_access().has_any_read(), - "DeferredWorld in system {} conflicts with a previous access.", - system_meta.name, - ); - component_access_set.write_all(); - } - - unsafe fn get_param<'world, 'state>( - _state: &'state mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - _change_tick: Tick, - ) -> Self::Item<'world, 'state> { - world.into_deferred() - } -} - -/// A system local [`SystemParam`]. -/// -/// A local may only be accessed by the system itself and is therefore not visible to other systems. -/// If two or more systems specify the same local type each will have their own unique local. -/// If multiple [`SystemParam`]s within the same system each specify the same local type -/// each will get their own distinct data storage. -/// -/// The supplied lifetime parameter is the [`SystemParam`]s `'s` lifetime. -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # let world = &mut World::default(); -/// fn write_to_local(mut local: Local) { -/// *local = 42; -/// } -/// fn read_from_local(local: Local) -> usize { -/// *local -/// } -/// let mut write_system = IntoSystem::into_system(write_to_local); -/// let mut read_system = IntoSystem::into_system(read_from_local); -/// write_system.initialize(world); -/// read_system.initialize(world); -/// -/// assert_eq!(read_system.run((), world).unwrap(), 0); -/// write_system.run((), world); -/// // Note how the read local is still 0 due to the locals not being shared. -/// assert_eq!(read_system.run((), world).unwrap(), 0); -/// ``` -/// -/// A simple way to set a different default value for a local is by wrapping the value with an Option. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # let world = &mut World::default(); -/// fn counter_from_10(mut count: Local>) -> usize { -/// let count = count.get_or_insert(10); -/// *count += 1; -/// *count -/// } -/// let mut counter_system = IntoSystem::into_system(counter_from_10); -/// counter_system.initialize(world); -/// -/// // Counter is initialized at 10, and increases to 11 on first run. -/// assert_eq!(counter_system.run((), world).unwrap(), 11); -/// // Counter is only increased by 1 on subsequent runs. -/// assert_eq!(counter_system.run((), world).unwrap(), 12); -/// ``` -/// -/// N.B. A [`Local`]s value cannot be read or written to outside of the containing system. -/// To add configuration to a system, convert a capturing closure into the system instead: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::assert_is_system; -/// struct Config(u32); -/// #[derive(Resource)] -/// struct MyU32Wrapper(u32); -/// fn reset_to_system(value: Config) -> impl FnMut(ResMut) { -/// move |mut val| val.0 = value.0 -/// } -/// -/// // .add_systems(reset_to_system(my_config)) -/// # assert_is_system(reset_to_system(Config(10))); -/// ``` -#[derive(Debug)] -pub struct Local<'s, T: FromWorld + Send + 'static>(pub(crate) &'s mut T); - -// SAFETY: Local only accesses internal state -unsafe impl<'s, T: FromWorld + Send + 'static> ReadOnlySystemParam for Local<'s, T> {} - -impl<'s, T: FromWorld + Send + 'static> Deref for Local<'s, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - self.0 - } -} - -impl<'s, T: FromWorld + Send + 'static> DerefMut for Local<'s, T> { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - self.0 - } -} - -impl<'s, 'a, T: FromWorld + Send + 'static> IntoIterator for &'a Local<'s, T> -where - &'a T: IntoIterator, -{ - type Item = <&'a T as IntoIterator>::Item; - type IntoIter = <&'a T as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'s, 'a, T: FromWorld + Send + 'static> IntoIterator for &'a mut Local<'s, T> -where - &'a mut T: IntoIterator, -{ - type Item = <&'a mut T as IntoIterator>::Item; - type IntoIter = <&'a mut T as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -// SAFETY: only local state is accessed -unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { - type State = SyncCell; - type Item<'w, 's> = Local<'s, T>; - - fn init_state(world: &mut World) -> Self::State { - SyncCell::new(T::from_world(world)) - } - - fn init_access( - _state: &Self::State, - _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - } - - #[inline] - unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, - _system_meta: &SystemMeta, - _world: UnsafeWorldCell<'w>, - _change_tick: Tick, - ) -> Self::Item<'w, 's> { - Local(state.get()) - } -} - -/// Types that can be used with [`Deferred`] in systems. -/// This allows storing system-local data which is used to defer [`World`] mutations. -/// -/// Types that implement `SystemBuffer` should take care to perform as many -/// computations up-front as possible. Buffers cannot be applied in parallel, -/// so you should try to minimize the time spent in [`SystemBuffer::apply`]. -pub trait SystemBuffer: FromWorld + Send + 'static { - /// Applies any deferred mutations to the [`World`]. - fn apply(&mut self, system_meta: &SystemMeta, world: &mut World); - /// Queues any deferred mutations to be applied at the next [`ApplyDeferred`](crate::prelude::ApplyDeferred). - fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) {} -} - -/// A [`SystemParam`] that stores a buffer which gets applied to the [`World`] during -/// [`ApplyDeferred`](crate::schedule::ApplyDeferred). -/// This is used internally by [`Commands`] to defer `World` mutations. -/// -/// [`Commands`]: crate::system::Commands -/// -/// # Examples -/// -/// By using this type to defer mutations, you can avoid mutable `World` access within -/// a system, which allows it to run in parallel with more systems. -/// -/// Note that deferring mutations is *not* free, and should only be used if -/// the gains in parallelization outweigh the time it takes to apply deferred mutations. -/// In general, [`Deferred`] should only be used for mutations that are infrequent, -/// or which otherwise take up a small portion of a system's run-time. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// // Tracks whether or not there is a threat the player should be aware of. -/// #[derive(Resource, Default)] -/// pub struct Alarm(bool); -/// -/// #[derive(Component)] -/// pub struct Settlement { -/// // ... -/// } -/// -/// // A threat from inside the settlement. -/// #[derive(Component)] -/// pub struct Criminal; -/// -/// // A threat from outside the settlement. -/// #[derive(Component)] -/// pub struct Monster; -/// -/// # impl Criminal { pub fn is_threat(&self, _: &Settlement) -> bool { true } } -/// -/// use bevy_ecs::system::{Deferred, SystemBuffer, SystemMeta}; -/// -/// // Uses deferred mutations to allow signaling the alarm from multiple systems in parallel. -/// #[derive(Resource, Default)] -/// struct AlarmFlag(bool); -/// -/// impl AlarmFlag { -/// /// Sounds the alarm the next time buffers are applied via ApplyDeferred. -/// pub fn flag(&mut self) { -/// self.0 = true; -/// } -/// } -/// -/// impl SystemBuffer for AlarmFlag { -/// // When `AlarmFlag` is used in a system, this function will get -/// // called the next time buffers are applied via ApplyDeferred. -/// fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) { -/// if self.0 { -/// world.resource_mut::().0 = true; -/// self.0 = false; -/// } -/// } -/// } -/// -/// // Sound the alarm if there are any criminals who pose a threat. -/// fn alert_criminal( -/// settlement: Single<&Settlement>, -/// criminals: Query<&Criminal>, -/// mut alarm: Deferred -/// ) { -/// for criminal in &criminals { -/// // Only sound the alarm if the criminal is a threat. -/// // For this example, assume that this check is expensive to run. -/// // Since the majority of this system's run-time is dominated -/// // by calling `is_threat()`, we defer sounding the alarm to -/// // allow this system to run in parallel with other alarm systems. -/// if criminal.is_threat(*settlement) { -/// alarm.flag(); -/// } -/// } -/// } -/// -/// // Sound the alarm if there is a monster. -/// fn alert_monster( -/// monsters: Query<&Monster>, -/// mut alarm: ResMut -/// ) { -/// if monsters.iter().next().is_some() { -/// // Since this system does nothing except for sounding the alarm, -/// // it would be pointless to defer it, so we sound the alarm directly. -/// alarm.0 = true; -/// } -/// } -/// -/// let mut world = World::new(); -/// world.init_resource::(); -/// world.spawn(Settlement { -/// // ... -/// }); -/// -/// let mut schedule = Schedule::default(); -/// // These two systems have no conflicts and will run in parallel. -/// schedule.add_systems((alert_criminal, alert_monster)); -/// -/// // There are no criminals or monsters, so the alarm is not sounded. -/// schedule.run(&mut world); -/// assert_eq!(world.resource::().0, false); -/// -/// // Spawn a monster, which will cause the alarm to be sounded. -/// let m_id = world.spawn(Monster).id(); -/// schedule.run(&mut world); -/// assert_eq!(world.resource::().0, true); -/// -/// // Remove the monster and reset the alarm. -/// world.entity_mut(m_id).despawn(); -/// world.resource_mut::().0 = false; -/// -/// // Spawn a criminal, which will cause the alarm to be sounded. -/// world.spawn(Criminal); -/// schedule.run(&mut world); -/// assert_eq!(world.resource::().0, true); -/// ``` -pub struct Deferred<'a, T: SystemBuffer>(pub(crate) &'a mut T); - -impl<'a, T: SystemBuffer> Deref for Deferred<'a, T> { - type Target = T; - #[inline] - fn deref(&self) -> &Self::Target { - self.0 - } -} - -impl<'a, T: SystemBuffer> DerefMut for Deferred<'a, T> { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - self.0 - } -} - -impl Deferred<'_, T> { - /// Returns a [`Deferred`] with a smaller lifetime. - /// This is useful if you have `&mut Deferred` but need `Deferred`. - pub fn reborrow(&mut self) -> Deferred { - Deferred(self.0) - } -} - -// SAFETY: Only local state is accessed. -unsafe impl ReadOnlySystemParam for Deferred<'_, T> {} - -// SAFETY: Only local state is accessed. -unsafe impl SystemParam for Deferred<'_, T> { - type State = SyncCell; - type Item<'w, 's> = Deferred<'s, T>; - - fn init_state(world: &mut World) -> Self::State { - SyncCell::new(T::from_world(world)) - } - - fn init_access( - _state: &Self::State, - system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - system_meta.set_has_deferred(); - } - - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { - state.get().apply(system_meta, world); - } - - fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { - state.get().queue(system_meta, world); - } - - #[inline] - unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, - _system_meta: &SystemMeta, - _world: UnsafeWorldCell<'w>, - _change_tick: Tick, - ) -> Self::Item<'w, 's> { - Deferred(state.get()) - } -} - -/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread. -pub struct NonSendMarker(PhantomData<*mut ()>); - -// SAFETY: No world access. -unsafe impl SystemParam for NonSendMarker { - type State = (); - type Item<'w, 's> = Self; - - #[inline] - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - system_meta.set_non_send(); - } - - #[inline] - unsafe fn get_param<'world, 'state>( - _state: &'state mut Self::State, - _system_meta: &SystemMeta, - _world: UnsafeWorldCell<'world>, - _change_tick: Tick, - ) -> Self::Item<'world, 'state> { - Self(PhantomData) - } -} - -// SAFETY: Does not read any world state -unsafe impl ReadOnlySystemParam for NonSendMarker {} - -/// Shared borrow of a non-[`Send`] resource. -/// -/// Only `Send` resources may be accessed with the [`Res`] [`SystemParam`]. In case that the -/// resource does not implement `Send`, this `SystemParam` wrapper can be used. This will instruct -/// the scheduler to instead run the system on the main thread so that it doesn't send the resource -/// over to another thread. -/// -/// This [`SystemParam`] fails validation if non-send resource doesn't exist. -/// This will cause a panic, but can be configured to do nothing or warn once. -/// -/// Use [`Option>`] instead if the resource might not always exist. -pub struct NonSend<'w, T: 'static> { - pub(crate) value: &'w T, - ticks: ComponentTicks, - last_run: Tick, - this_run: Tick, - changed_by: MaybeLocation<&'w &'static Location<'static>>, -} - -// SAFETY: Only reads a single World non-send resource -unsafe impl<'w, T> ReadOnlySystemParam for NonSend<'w, T> {} - -impl<'w, T> Debug for NonSend<'w, T> -where - T: Debug, -{ - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("NonSend").field(&self.value).finish() - } -} - -impl<'w, T: 'static> NonSend<'w, T> { - /// Returns `true` if the resource was added after the system last ran. - pub fn is_added(&self) -> bool { - self.ticks.is_added(self.last_run, self.this_run) - } - - /// Returns `true` if the resource was added or mutably dereferenced after the system last ran. - pub fn is_changed(&self) -> bool { - self.ticks.is_changed(self.last_run, self.this_run) - } - - /// The location that last caused this to change. - pub fn changed_by(&self) -> MaybeLocation { - self.changed_by.copied() - } -} - -impl<'w, T> Deref for NonSend<'w, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.value - } -} - -impl<'a, T> From> for NonSend<'a, T> { - fn from(nsm: NonSendMut<'a, T>) -> Self { - Self { - value: nsm.value, - ticks: ComponentTicks { - added: nsm.ticks.added.to_owned(), - changed: nsm.ticks.changed.to_owned(), - }, - this_run: nsm.ticks.this_run, - last_run: nsm.ticks.last_run, - changed_by: nsm.changed_by.map(|changed_by| &*changed_by), - } - } -} - -// SAFETY: NonSendComponentId access is applied to SystemMeta. If this -// NonSend conflicts with any prior access, a panic will occur. -unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { - type State = ComponentId; - type Item<'w, 's> = NonSend<'w, T>; - - fn init_state(world: &mut World) -> Self::State { - world.components_registrator().register_non_send::() - } - - fn init_access( - &component_id: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - system_meta.set_non_send(); - - let combined_access = component_access_set.combined_access(); - assert!( - !combined_access.has_resource_write(component_id), - "error[B0002]: NonSend<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", - DebugName::type_name::(), - system_meta.name, - ); - component_access_set.add_unfiltered_resource_read(component_id); - } - - #[inline] - unsafe fn validate_param( - &mut component_id: &mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // SAFETY: Read-only access to resource metadata. - if unsafe { world.storages() } - .non_send_resources - .get(component_id) - .is_some_and(ResourceData::is_present) - { - Ok(()) - } else { - Err(SystemParamValidationError::invalid::( - "Non-send resource does not exist", - )) - } - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - let (ptr, ticks, caller) = - world - .get_non_send_with_ticks(component_id) - .unwrap_or_else(|| { - panic!( - "Non-send resource requested by {} does not exist: {}", - system_meta.name, - DebugName::type_name::() - ) - }); - - NonSend { - value: ptr.deref(), - ticks: ticks.read(), - last_run: system_meta.last_run, - this_run: change_tick, - changed_by: caller.map(|caller| caller.deref()), - } - } -} - -// SAFETY: NonSendMut ComponentId access is applied to SystemMeta. If this -// NonSendMut conflicts with any prior access, a panic will occur. -unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { - type State = ComponentId; - type Item<'w, 's> = NonSendMut<'w, T>; - - fn init_state(world: &mut World) -> Self::State { - world.components_registrator().register_non_send::() - } - - fn init_access( - &component_id: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - system_meta.set_non_send(); - - let combined_access = component_access_set.combined_access(); - if combined_access.has_component_write(component_id) { - panic!( - "error[B0002]: NonSendMut<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", - DebugName::type_name::(), system_meta.name); - } else if combined_access.has_component_read(component_id) { - panic!( - "error[B0002]: NonSendMut<{}> in system {} conflicts with a previous immutable resource access ({0}). Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", - DebugName::type_name::(), system_meta.name); - } - component_access_set.add_unfiltered_resource_write(component_id); - } - - #[inline] - unsafe fn validate_param( - &mut component_id: &mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // SAFETY: Read-only access to resource metadata. - if unsafe { world.storages() } - .non_send_resources - .get(component_id) - .is_some_and(ResourceData::is_present) - { - Ok(()) - } else { - Err(SystemParamValidationError::invalid::( - "Non-send resource does not exist", - )) - } - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - let (ptr, ticks, caller) = - world - .get_non_send_with_ticks(component_id) - .unwrap_or_else(|| { - panic!( - "Non-send resource requested by {} does not exist: {}", - system_meta.name, - DebugName::type_name::() - ); - }); - NonSendMut { - value: ptr.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick), - changed_by: caller.map(|caller| caller.deref_mut()), - } - } -} - -// SAFETY: Only reads World archetypes -unsafe impl<'a> ReadOnlySystemParam for &'a Archetypes {} - -// SAFETY: no component value access -unsafe impl<'a> SystemParam for &'a Archetypes { - type State = (); - type Item<'w, 's> = &'w Archetypes; - - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - } - - #[inline] - unsafe fn get_param<'w, 's>( - _state: &'s mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - _change_tick: Tick, - ) -> Self::Item<'w, 's> { - world.archetypes() - } -} - -// SAFETY: Only reads World components -unsafe impl<'a> ReadOnlySystemParam for &'a Components {} - -// SAFETY: no component value access -unsafe impl<'a> SystemParam for &'a Components { - type State = (); - type Item<'w, 's> = &'w Components; - - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - } - - #[inline] - unsafe fn get_param<'w, 's>( - _state: &'s mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - _change_tick: Tick, - ) -> Self::Item<'w, 's> { - world.components() - } -} - -// SAFETY: Only reads World entities -unsafe impl<'a> ReadOnlySystemParam for &'a Entities {} - -// SAFETY: no component value access -unsafe impl<'a> SystemParam for &'a Entities { - type State = (); - type Item<'w, 's> = &'w Entities; - - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - } - - #[inline] - unsafe fn get_param<'w, 's>( - _state: &'s mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - _change_tick: Tick, - ) -> Self::Item<'w, 's> { - world.entities() - } -} - -// SAFETY: Only reads World bundles -unsafe impl<'a> ReadOnlySystemParam for &'a Bundles {} - -// SAFETY: no component value access -unsafe impl<'a> SystemParam for &'a Bundles { - type State = (); - type Item<'w, 's> = &'w Bundles; - - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - } - - #[inline] - unsafe fn get_param<'w, 's>( - _state: &'s mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - _change_tick: Tick, - ) -> Self::Item<'w, 's> { - world.bundles() - } -} - -/// A [`SystemParam`] that reads the previous and current change ticks of the system. -/// -/// A system's change ticks are updated each time it runs: -/// - `last_run` copies the previous value of `change_tick` -/// - `this_run` copies the current value of [`World::read_change_tick`] -/// -/// Component change ticks that are more recent than `last_run` will be detected by the system. -/// Those can be read by calling [`last_changed`](crate::change_detection::DetectChanges::last_changed) -/// on a [`Mut`](crate::change_detection::Mut) or [`ResMut`](ResMut). -#[derive(Debug, Clone, Copy)] -pub struct SystemChangeTick { - last_run: Tick, - this_run: Tick, -} - -impl SystemChangeTick { - /// Returns the current [`World`] change tick seen by the system. - #[inline] - pub fn this_run(&self) -> Tick { - self.this_run - } - - /// Returns the [`World`] change tick seen by the system the previous time it ran. - #[inline] - pub fn last_run(&self) -> Tick { - self.last_run - } -} - -// SAFETY: Only reads internal system state -unsafe impl ReadOnlySystemParam for SystemChangeTick {} - -// SAFETY: `SystemChangeTick` doesn't require any world access -unsafe impl SystemParam for SystemChangeTick { - type State = (); - type Item<'w, 's> = SystemChangeTick; - - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - } - - #[inline] - unsafe fn get_param<'w, 's>( - _state: &'s mut Self::State, - system_meta: &SystemMeta, - _world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - SystemChangeTick { - last_run: system_meta.last_run, - this_run: change_tick, - } - } -} - -// SAFETY: Delegates to `T`, which ensures the safety requirements are met -unsafe impl SystemParam for Option { - type State = T::State; - - type Item<'world, 'state> = Option>; - - fn init_state(world: &mut World) -> Self::State { - T::init_state(world) - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - T::init_access(state, system_meta, component_access_set, world); - } - - #[inline] - unsafe fn get_param<'world, 'state>( - state: &'state mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - change_tick: Tick, - ) -> Self::Item<'world, 'state> { - T::validate_param(state, system_meta, world) - .ok() - .map(|()| T::get_param(state, system_meta, world, change_tick)) - } - - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { - T::apply(state, system_meta, world); - } - - fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { - T::queue(state, system_meta, world); - } -} - -// SAFETY: Delegates to `T`, which ensures the safety requirements are met -unsafe impl ReadOnlySystemParam for Option {} - -// SAFETY: Delegates to `T`, which ensures the safety requirements are met -unsafe impl SystemParam for Result { - type State = T::State; - - type Item<'world, 'state> = Result, SystemParamValidationError>; - - fn init_state(world: &mut World) -> Self::State { - T::init_state(world) - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - T::init_access(state, system_meta, component_access_set, world); - } - - #[inline] - unsafe fn get_param<'world, 'state>( - state: &'state mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - change_tick: Tick, - ) -> Self::Item<'world, 'state> { - T::validate_param(state, system_meta, world) - .map(|()| T::get_param(state, system_meta, world, change_tick)) - } - - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { - T::apply(state, system_meta, world); - } - - fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { - T::queue(state, system_meta, world); - } -} - -// SAFETY: Delegates to `T`, which ensures the safety requirements are met -unsafe impl ReadOnlySystemParam for Result {} - -/// A [`SystemParam`] that wraps another parameter and causes its system to skip instead of failing when the parameter is invalid. -/// -/// # Example -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # #[derive(Resource)] -/// # struct SomeResource; -/// // This system will fail if `SomeResource` is not present. -/// fn fails_on_missing_resource(res: Res) {} -/// -/// // This system will skip without error if `SomeResource` is not present. -/// fn skips_on_missing_resource(res: When>) { -/// // The inner parameter is available using `Deref` -/// let some_resource: &SomeResource = &res; -/// } -/// # bevy_ecs::system::assert_is_system(skips_on_missing_resource); -/// ``` -#[derive(Debug)] -pub struct When(pub T); - -impl When { - /// Returns the inner `T`. - /// - /// The inner value is `pub`, so you can also obtain it by destructuring the parameter: - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource)] - /// # struct SomeResource; - /// fn skips_on_missing_resource(When(res): When>) { - /// let some_resource: Res = res; - /// } - /// # bevy_ecs::system::assert_is_system(skips_on_missing_resource); - /// ``` - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for When { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for When { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -// SAFETY: Delegates to `T`, which ensures the safety requirements are met -unsafe impl SystemParam for When { - type State = T::State; - - type Item<'world, 'state> = When>; - - fn init_state(world: &mut World) -> Self::State { - T::init_state(world) - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - T::init_access(state, system_meta, component_access_set, world); - } - - #[inline] - unsafe fn validate_param( - state: &mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - T::validate_param(state, system_meta, world).map_err(|mut e| { - e.skipped = true; - e - }) - } - - #[inline] - unsafe fn get_param<'world, 'state>( - state: &'state mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - change_tick: Tick, - ) -> Self::Item<'world, 'state> { - When(T::get_param(state, system_meta, world, change_tick)) - } - - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { - T::apply(state, system_meta, world); - } - - fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { - T::queue(state, system_meta, world); - } -} - -// SAFETY: Delegates to `T`, which ensures the safety requirements are met -unsafe impl ReadOnlySystemParam for When {} - -// SAFETY: Registers access for each element of `state`. -// If any one conflicts, it will panic. -unsafe impl SystemParam for Vec { - type State = Vec; - - type Item<'world, 'state> = Vec>; - - fn init_state(_world: &mut World) -> Self::State { - Vec::new() - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - for state in state { - T::init_access(state, system_meta, component_access_set, world); - } - } - - #[inline] - unsafe fn validate_param( - state: &mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - for state in state { - T::validate_param(state, system_meta, world)?; - } - Ok(()) - } - - #[inline] - unsafe fn get_param<'world, 'state>( - state: &'state mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - change_tick: Tick, - ) -> Self::Item<'world, 'state> { - state - .iter_mut() - // SAFETY: - // - We initialized the access for each parameter in `init_access`, so the caller ensures we have access to any world data needed by each param. - // - The caller ensures this was the world used to initialize our state, and we used that world to initialize parameter states - .map(|state| unsafe { T::get_param(state, system_meta, world, change_tick) }) - .collect() - } - - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { - for state in state { - T::apply(state, system_meta, world); - } - } - - fn queue(state: &mut Self::State, system_meta: &SystemMeta, mut world: DeferredWorld) { - for state in state { - T::queue(state, system_meta, world.reborrow()); - } - } -} - -// SAFETY: Registers access for each element of `state`. -// If any one conflicts with a previous parameter, -// the call passing a copy of the current access will panic. -unsafe impl SystemParam for ParamSet<'_, '_, Vec> { - type State = Vec; - - type Item<'world, 'state> = ParamSet<'world, 'state, Vec>; - - fn init_state(_world: &mut World) -> Self::State { - Vec::new() - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - for state in state { - // Call `init_access` on a clone of the original access set to check for conflicts - let component_access_set_clone = &mut component_access_set.clone(); - T::init_access(state, system_meta, component_access_set_clone, world); - } - for state in state { - // Pretend to add the param to the system alone to gather the new access, - // then merge its access into the system. - let mut access_set = FilteredAccessSet::new(); - T::init_access(state, system_meta, &mut access_set, world); - component_access_set.extend(access_set); - } - } - - #[inline] - unsafe fn get_param<'world, 'state>( - state: &'state mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - change_tick: Tick, - ) -> Self::Item<'world, 'state> { - ParamSet { - param_states: state, - system_meta: system_meta.clone(), - world, - change_tick, - } - } - - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { - for state in state { - T::apply(state, system_meta, world); - } - } - - fn queue(state: &mut Self::State, system_meta: &SystemMeta, mut world: DeferredWorld) { - for state in state { - T::queue(state, system_meta, world.reborrow()); - } - } -} - -impl ParamSet<'_, '_, Vec> { - /// Accesses the parameter at the given index. - /// No other parameters may be accessed while this one is active. - pub fn get_mut(&mut self, index: usize) -> T::Item<'_, '_> { - // SAFETY: - // - We initialized the access for each parameter, so the caller ensures we have access to any world data needed by any param. - // We have mutable access to the ParamSet, so no other params in the set are active. - // - The caller of `get_param` ensured that this was the world used to initialize our state, and we used that world to initialize parameter states - unsafe { - T::get_param( - &mut self.param_states[index], - &self.system_meta, - self.world, - self.change_tick, - ) - } - } - - /// Calls a closure for each parameter in the set. - pub fn for_each(&mut self, mut f: impl FnMut(T::Item<'_, '_>)) { - self.param_states.iter_mut().for_each(|state| { - f( - // SAFETY: - // - We initialized the access for each parameter, so the caller ensures we have access to any world data needed by any param. - // We have mutable access to the ParamSet, so no other params in the set are active. - // - The caller of `get_param` ensured that this was the world used to initialize our state, and we used that world to initialize parameter states - unsafe { T::get_param(state, &self.system_meta, self.world, self.change_tick) }, - ); - }); - } -} - -macro_rules! impl_system_param_tuple { - ($(#[$meta:meta])* $($param: ident),*) => { - $(#[$meta])* - // SAFETY: tuple consists only of ReadOnlySystemParams - unsafe impl<$($param: ReadOnlySystemParam),*> ReadOnlySystemParam for ($($param,)*) {} - - #[expect( - clippy::allow_attributes, - reason = "This is in a macro, and as such, the below lints may not always apply." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use some of the parameters." - )] - $(#[$meta])* - // SAFETY: implementers of each `SystemParam` in the tuple have validated their impls - unsafe impl<$($param: SystemParam),*> SystemParam for ($($param,)*) { - type State = ($($param::State,)*); - type Item<'w, 's> = ($($param::Item::<'w, 's>,)*); - - #[inline] - fn init_state(world: &mut World) -> Self::State { - (($($param::init_state(world),)*)) - } - - fn init_access(state: &Self::State, _system_meta: &mut SystemMeta, _component_access_set: &mut FilteredAccessSet, _world: &mut World) { - let ($($param,)*) = state; - $($param::init_access($param, _system_meta, _component_access_set, _world);)* - } - - #[inline] - fn apply(($($param,)*): &mut Self::State, system_meta: &SystemMeta, world: &mut World) { - $($param::apply($param, system_meta, world);)* - } - - #[inline] - #[allow( - unused_mut, - reason = "The `world` parameter is unused for zero-length tuples; however, it must be mutable for other lengths of tuples." - )] - fn queue(($($param,)*): &mut Self::State, system_meta: &SystemMeta, mut world: DeferredWorld) { - $($param::queue($param, system_meta, world.reborrow());)* - } - - #[inline] - unsafe fn validate_param( - state: &mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - let ($($param,)*) = state; - $( - $param::validate_param($param, system_meta, world)?; - )* - Ok(()) - } - - #[inline] - unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - let ($($param,)*) = state; - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples won't have any params to get." - )] - ($($param::get_param($param, system_meta, world, change_tick),)*) - } - } - }; -} - -all_tuples!( - #[doc(fake_variadic)] - impl_system_param_tuple, - 0, - 16, - P -); - -/// Contains type aliases for built-in [`SystemParam`]s with `'static` lifetimes. -/// This makes it more convenient to refer to these types in contexts where -/// explicit lifetime annotations are required. -/// -/// Note that this is entirely safe and tracks lifetimes correctly. -/// This purely exists for convenience. -/// -/// You can't instantiate a static `SystemParam`, you'll always end up with -/// `Res<'w, T>`, `ResMut<'w, T>` or `&'w T` bound to the lifetime of the provided -/// `&'w World`. -/// -/// [`SystemParam`]: super::SystemParam -pub mod lifetimeless { - /// A [`Query`](super::Query) with `'static` lifetimes. - pub type SQuery = super::Query<'static, 'static, D, F>; - /// A shorthand for writing `&'static T`. - pub type Read = &'static T; - /// A shorthand for writing `&'static mut T`. - pub type Write = &'static mut T; - /// A [`Res`](super::Res) with `'static` lifetimes. - pub type SRes = super::Res<'static, T>; - /// A [`ResMut`](super::ResMut) with `'static` lifetimes. - pub type SResMut = super::ResMut<'static, T>; - /// [`Commands`](crate::system::Commands) with `'static` lifetimes. - pub type SCommands = crate::system::Commands<'static, 'static>; -} - -/// A helper for using system parameters in generic contexts -/// -/// This type is a [`SystemParam`] adapter which always has -/// `Self::Item == Self` (ignoring lifetimes for brevity), -/// no matter the argument [`SystemParam`] (`P`) (other than -/// that `P` must be `'static`) -/// -/// This makes it useful for having arbitrary [`SystemParam`] type arguments -/// to function systems, or for generic types using the [`derive@SystemParam`] -/// derive: -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// use bevy_ecs::system::{SystemParam, StaticSystemParam}; -/// #[derive(SystemParam)] -/// struct GenericParam<'w,'s, T: SystemParam + 'static> { -/// field: StaticSystemParam<'w, 's, T>, -/// } -/// fn do_thing_generically(t: StaticSystemParam) {} -/// -/// fn check_always_is_system(){ -/// bevy_ecs::system::assert_is_system(do_thing_generically::); -/// } -/// ``` -/// Note that in a real case you'd generally want -/// additional bounds on `P`, for your use of the parameter -/// to have a reason to be generic. -/// -/// For example, using this would allow a type to be generic over -/// whether a resource is accessed mutably or not, with -/// impls being bounded on [`P: Deref`](Deref), and -/// [`P: DerefMut`](DerefMut) depending on whether the -/// method requires mutable access or not. -/// -/// The method which doesn't use this type will not compile: -/// ```compile_fail -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::{SystemParam, StaticSystemParam}; -/// -/// fn do_thing_generically(t: T) {} -/// -/// #[derive(SystemParam)] -/// struct GenericParam<'w, 's, T: SystemParam> { -/// field: T, -/// // Use the lifetimes in this type, or they will be unbound. -/// phantom: std::marker::PhantomData<&'w &'s ()> -/// } -/// # fn check_always_is_system(){ -/// # bevy_ecs::system::assert_is_system(do_thing_generically::); -/// # } -/// ``` -pub struct StaticSystemParam<'w, 's, P: SystemParam>(SystemParamItem<'w, 's, P>); - -impl<'w, 's, P: SystemParam> Deref for StaticSystemParam<'w, 's, P> { - type Target = SystemParamItem<'w, 's, P>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'w, 's, P: SystemParam> DerefMut for StaticSystemParam<'w, 's, P> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl<'w, 's, P: SystemParam> StaticSystemParam<'w, 's, P> { - /// Get the value of the parameter - pub fn into_inner(self) -> SystemParamItem<'w, 's, P> { - self.0 - } -} - -// SAFETY: This doesn't add any more reads, and the delegated fetch confirms it -unsafe impl<'w, 's, P: ReadOnlySystemParam + 'static> ReadOnlySystemParam - for StaticSystemParam<'w, 's, P> -{ -} - -// SAFETY: all methods are just delegated to `P`'s `SystemParam` implementation -unsafe impl SystemParam for StaticSystemParam<'_, '_, P> { - type State = P::State; - type Item<'world, 'state> = StaticSystemParam<'world, 'state, P>; - - fn init_state(world: &mut World) -> Self::State { - P::init_state(world) - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - P::init_access(state, system_meta, component_access_set, world); - } - - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { - P::apply(state, system_meta, world); - } - - fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { - P::queue(state, system_meta, world); - } - - #[inline] - unsafe fn validate_param( - state: &mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - P::validate_param(state, system_meta, world) - } - - #[inline] - unsafe fn get_param<'world, 'state>( - state: &'state mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - change_tick: Tick, - ) -> Self::Item<'world, 'state> { - // SAFETY: Defer to the safety of P::SystemParam - StaticSystemParam(unsafe { P::get_param(state, system_meta, world, change_tick) }) - } -} - -// SAFETY: No world access. -unsafe impl SystemParam for PhantomData { - type State = (); - type Item<'world, 'state> = Self; - - fn init_state(_world: &mut World) -> Self::State {} - - fn init_access( - _state: &Self::State, - _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, - _world: &mut World, - ) { - } - - #[inline] - unsafe fn get_param<'world, 'state>( - _state: &'state mut Self::State, - _system_meta: &SystemMeta, - _world: UnsafeWorldCell<'world>, - _change_tick: Tick, - ) -> Self::Item<'world, 'state> { - PhantomData - } -} - -// SAFETY: No world access. -unsafe impl ReadOnlySystemParam for PhantomData {} - -/// A [`SystemParam`] with a type that can be configured at runtime. -/// -/// To be useful, this must be configured using a [`DynParamBuilder`](crate::system::DynParamBuilder) to build the system using a [`SystemParamBuilder`](crate::prelude::SystemParamBuilder). -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::{prelude::*, system::*}; -/// # -/// # #[derive(Default, Resource)] -/// # struct A; -/// # -/// # #[derive(Default, Resource)] -/// # struct B; -/// # -/// # let mut world = World::new(); -/// # world.init_resource::(); -/// # world.init_resource::(); -/// # -/// // If the inner parameter doesn't require any special building, use `ParamBuilder`. -/// // Either specify the type parameter on `DynParamBuilder::new()` ... -/// let system = (DynParamBuilder::new::>(ParamBuilder),) -/// .build_state(&mut world) -/// .build_system(expects_res_a); -/// # world.run_system_once(system); -/// -/// // ... or use a factory method on `ParamBuilder` that returns a specific type. -/// let system = (DynParamBuilder::new(ParamBuilder::resource::()),) -/// .build_state(&mut world) -/// .build_system(expects_res_a); -/// # world.run_system_once(system); -/// -/// fn expects_res_a(mut param: DynSystemParam) { -/// // Use the `downcast` methods to retrieve the inner parameter. -/// // They will return `None` if the type does not match. -/// assert!(param.is::>()); -/// assert!(!param.is::>()); -/// assert!(param.downcast_mut::>().is_none()); -/// let res = param.downcast_mut::>().unwrap(); -/// // The type parameter can be left out if it can be determined from use. -/// let res: Res = param.downcast().unwrap(); -/// } -/// -/// let system = ( -/// // If the inner parameter also requires building, -/// // pass the appropriate `SystemParamBuilder`. -/// DynParamBuilder::new(LocalBuilder(10usize)), -/// // `DynSystemParam` is just an ordinary `SystemParam`, -/// // and can be combined with other parameters as usual! -/// ParamBuilder::query(), -/// ) -/// .build_state(&mut world) -/// .build_system(|param: DynSystemParam, query: Query<()>| { -/// let local: Local = param.downcast::>().unwrap(); -/// assert_eq!(*local, 10); -/// }); -/// # world.run_system_once(system); -/// ``` -pub struct DynSystemParam<'w, 's> { - /// A `ParamState` wrapping the state for the underlying system param. - state: &'s mut dyn Any, - world: UnsafeWorldCell<'w>, - system_meta: SystemMeta, - change_tick: Tick, -} - -impl<'w, 's> DynSystemParam<'w, 's> { - /// # Safety - /// - `state` must be a `ParamState` for some inner `T: SystemParam`. - /// - The passed [`UnsafeWorldCell`] must have access to any world data registered - /// in [`init_state`](SystemParam::init_state) for the inner system param. - /// - `world` must be the same `World` that was used to initialize - /// [`state`](SystemParam::init_state) for the inner system param. - unsafe fn new( - state: &'s mut dyn Any, - world: UnsafeWorldCell<'w>, - system_meta: SystemMeta, - change_tick: Tick, - ) -> Self { - Self { - state, - world, - system_meta, - change_tick, - } - } - - /// Returns `true` if the inner system param is the same as `T`. - pub fn is(&self) -> bool - // See downcast() function for an explanation of the where clause - where - T::Item<'static, 'static>: SystemParam = T> + 'static, - { - self.state.is::>>() - } - - /// Returns the inner system param if it is the correct type. - /// This consumes the dyn param, so the returned param can have its original world and state lifetimes. - pub fn downcast(self) -> Option - // See downcast() function for an explanation of the where clause - where - T::Item<'static, 'static>: SystemParam = T> + 'static, - { - // SAFETY: - // - `DynSystemParam::new()` ensures `state` is a `ParamState`, that the world matches, - // and that it has access required by the inner system param. - // - This consumes the `DynSystemParam`, so it is the only use of `world` with this access and it is available for `'w`. - unsafe { downcast::(self.state, &self.system_meta, self.world, self.change_tick) } - } - - /// Returns the inner system parameter if it is the correct type. - /// This borrows the dyn param, so the returned param is only valid for the duration of that borrow. - pub fn downcast_mut<'a, T: SystemParam>(&'a mut self) -> Option - // See downcast() function for an explanation of the where clause - where - T::Item<'static, 'static>: SystemParam = T> + 'static, - { - // SAFETY: - // - `DynSystemParam::new()` ensures `state` is a `ParamState`, that the world matches, - // and that it has access required by the inner system param. - // - This exclusively borrows the `DynSystemParam` for `'_`, so it is the only use of `world` with this access for `'_`. - unsafe { downcast::(self.state, &self.system_meta, self.world, self.change_tick) } - } - - /// Returns the inner system parameter if it is the correct type. - /// This borrows the dyn param, so the returned param is only valid for the duration of that borrow, - /// but since it only performs read access it can keep the original world lifetime. - /// This can be useful with methods like [`Query::iter_inner()`] or [`Res::into_inner()`] - /// to obtain references with the original world lifetime. - pub fn downcast_mut_inner<'a, T: ReadOnlySystemParam>(&'a mut self) -> Option - // See downcast() function for an explanation of the where clause - where - T::Item<'static, 'static>: SystemParam = T> + 'static, - { - // SAFETY: - // - `DynSystemParam::new()` ensures `state` is a `ParamState`, that the world matches, - // and that it has access required by the inner system param. - // - The inner system param only performs read access, so it's safe to copy that access for the full `'w` lifetime. - unsafe { downcast::(self.state, &self.system_meta, self.world, self.change_tick) } - } -} - -/// # Safety -/// - `state` must be a `ParamState` for some inner `T: SystemParam`. -/// - The passed [`UnsafeWorldCell`] must have access to any world data registered -/// in [`init_state`](SystemParam::init_state) for the inner system param. -/// - `world` must be the same `World` that was used to initialize -/// [`state`](SystemParam::init_state) for the inner system param. -unsafe fn downcast<'w, 's, T: SystemParam>( - state: &'s mut dyn Any, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, -) -> Option -// We need a 'static version of the SystemParam to use with `Any::downcast_mut()`, -// and we need a <'w, 's> version to actually return. -// The type parameter T must be the one we return in order to get type inference from the return value. -// So we use `T::Item<'static, 'static>` as the 'static version, and require that it be 'static. -// That means the return value will be T::Item<'static, 'static>::Item<'w, 's>, -// so we constrain that to be equal to T. -// Every actual `SystemParam` implementation has `T::Item == T` up to lifetimes, -// so they should all work with this constraint. -where - T::Item<'static, 'static>: SystemParam = T> + 'static, -{ - state - .downcast_mut::>>() - .map(|state| { - // SAFETY: - // - The caller ensures the world has access for the underlying system param, - // and since the downcast succeeded, the underlying system param is T. - // - The caller ensures the `world` matches. - unsafe { T::Item::get_param(&mut state.0, system_meta, world, change_tick) } - }) -} - -/// The [`SystemParam::State`] for a [`DynSystemParam`]. -pub struct DynSystemParamState(Box); - -impl DynSystemParamState { - pub(crate) fn new(state: T::State) -> Self { - Self(Box::new(ParamState::(state))) - } -} - -/// Allows a [`SystemParam::State`] to be used as a trait object for implementing [`DynSystemParam`]. -trait DynParamState: Sync + Send + Any { - /// Applies any deferred mutations stored in this [`SystemParam`]'s state. - /// This is used to apply [`Commands`] during [`ApplyDeferred`](crate::prelude::ApplyDeferred). - /// - /// [`Commands`]: crate::prelude::Commands - fn apply(&mut self, system_meta: &SystemMeta, world: &mut World); - - /// Queues any deferred mutations to be applied at the next [`ApplyDeferred`](crate::prelude::ApplyDeferred). - fn queue(&mut self, system_meta: &SystemMeta, world: DeferredWorld); - - /// Registers any [`World`] access used by this [`SystemParam`] - fn init_access( - &self, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ); - - /// Refer to [`SystemParam::validate_param`]. - /// - /// # Safety - /// Refer to [`SystemParam::validate_param`]. - unsafe fn validate_param( - &mut self, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError>; -} - -/// A wrapper around a [`SystemParam::State`] that can be used as a trait object in a [`DynSystemParam`]. -struct ParamState(T::State); - -impl DynParamState for ParamState { - fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) { - T::apply(&mut self.0, system_meta, world); - } - - fn queue(&mut self, system_meta: &SystemMeta, world: DeferredWorld) { - T::queue(&mut self.0, system_meta, world); - } - - fn init_access( - &self, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - T::init_access(&self.0, system_meta, component_access_set, world); - } - - unsafe fn validate_param( - &mut self, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - T::validate_param(&mut self.0, system_meta, world) - } -} - -// SAFETY: Delegates to the wrapped parameter, which ensures the safety requirements are met -unsafe impl SystemParam for DynSystemParam<'_, '_> { - type State = DynSystemParamState; - - type Item<'world, 'state> = DynSystemParam<'world, 'state>; - - fn init_state(_world: &mut World) -> Self::State { - DynSystemParamState::new::<()>(()) - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - state - .0 - .init_access(system_meta, component_access_set, world); - } - - #[inline] - unsafe fn validate_param( - state: &mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - state.0.validate_param(system_meta, world) - } - - #[inline] - unsafe fn get_param<'world, 'state>( - state: &'state mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - change_tick: Tick, - ) -> Self::Item<'world, 'state> { - // SAFETY: - // - `state.0` is a boxed `ParamState`. - // - `init_access` calls `DynParamState::init_access`, which calls `init_access` on the inner parameter, - // so the caller ensures the world has the necessary access. - // - The caller ensures that the provided world is the same and has the required access. - unsafe { DynSystemParam::new(state.0.as_mut(), world, system_meta.clone(), change_tick) } - } - - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { - state.0.apply(system_meta, world); - } - - fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { - state.0.queue(system_meta, world); - } -} - -// SAFETY: Resource ComponentId access is applied to the access. If this FilteredResources -// conflicts with any prior access, a panic will occur. -unsafe impl SystemParam for FilteredResources<'_, '_> { - type State = Access; - - type Item<'world, 'state> = FilteredResources<'world, 'state>; - - fn init_state(_world: &mut World) -> Self::State { - Access::new() - } - - fn init_access( - access: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - let combined_access = component_access_set.combined_access(); - let conflicts = combined_access.get_conflicts(access); - if !conflicts.is_empty() { - let accesses = conflicts.format_conflict_list(world); - let system_name = &system_meta.name; - panic!("error[B0002]: FilteredResources in system {system_name} accesses resources(s){accesses} in a way that conflicts with a previous system parameter. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002"); - } - - if access.has_read_all_resources() { - component_access_set.add_unfiltered_read_all_resources(); - } else { - for component_id in access.resource_reads_and_writes() { - component_access_set.add_unfiltered_resource_read(component_id); - } - } - } - - unsafe fn get_param<'world, 'state>( - state: &'state mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - change_tick: Tick, - ) -> Self::Item<'world, 'state> { - // SAFETY: The caller ensures that `world` has access to anything registered in `init_access`, - // and we registered all resource access in `state``. - unsafe { FilteredResources::new(world, state, system_meta.last_run, change_tick) } - } -} - -// SAFETY: FilteredResources only reads resources. -unsafe impl ReadOnlySystemParam for FilteredResources<'_, '_> {} - -// SAFETY: Resource ComponentId access is applied to the access. If this FilteredResourcesMut -// conflicts with any prior access, a panic will occur. -unsafe impl SystemParam for FilteredResourcesMut<'_, '_> { - type State = Access; - - type Item<'world, 'state> = FilteredResourcesMut<'world, 'state>; - - fn init_state(_world: &mut World) -> Self::State { - Access::new() - } - - fn init_access( - access: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - let combined_access = component_access_set.combined_access(); - let conflicts = combined_access.get_conflicts(access); - if !conflicts.is_empty() { - let accesses = conflicts.format_conflict_list(world); - let system_name = &system_meta.name; - panic!("error[B0002]: FilteredResourcesMut in system {system_name} accesses resources(s){accesses} in a way that conflicts with a previous system parameter. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002"); - } - - if access.has_read_all_resources() { - component_access_set.add_unfiltered_read_all_resources(); - } else { - for component_id in access.resource_reads() { - component_access_set.add_unfiltered_resource_read(component_id); - } - } - - if access.has_write_all_resources() { - component_access_set.add_unfiltered_write_all_resources(); - } else { - for component_id in access.resource_writes() { - component_access_set.add_unfiltered_resource_write(component_id); - } - } - } - - unsafe fn get_param<'world, 'state>( - state: &'state mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - change_tick: Tick, - ) -> Self::Item<'world, 'state> { - // SAFETY: The caller ensures that `world` has access to anything registered in `init_access`, - // and we registered all resource access in `state``. - unsafe { FilteredResourcesMut::new(world, state, system_meta.last_run, change_tick) } - } -} - -/// An error that occurs when a system parameter is not valid, -/// used by system executors to determine what to do with a system. -/// -/// Returned as an error from [`SystemParam::validate_param`], -/// and handled using the unified error handling mechanisms defined in [`bevy_ecs::error`]. -#[derive(Debug, PartialEq, Eq, Clone, Error)] -pub struct SystemParamValidationError { - /// Whether the system should be skipped. - /// - /// If `false`, the error should be handled. - /// By default, this will result in a panic. See [`crate::error`] for more information. - /// - /// This is the default behavior, and is suitable for system params that should *always* be valid, - /// either because sensible fallback behavior exists (like [`Query`]) or because - /// failures in validation should be considered a bug in the user's logic that must be immediately addressed (like [`Res`]). - /// - /// If `true`, the system should be skipped. - /// This is set by wrapping the system param in [`When`], - /// and indicates that the system is intended to only operate in certain application states. - pub skipped: bool, - - /// A message describing the validation error. - pub message: Cow<'static, str>, - - /// A string identifying the invalid parameter. - /// This is usually the type name of the parameter. - pub param: DebugName, - - /// A string identifying the field within a parameter using `#[derive(SystemParam)]`. - /// This will be an empty string for other parameters. - /// - /// This will be printed after `param` in the `Display` impl, and should include a `::` prefix if non-empty. - pub field: Cow<'static, str>, -} - -impl SystemParamValidationError { - /// Constructs a `SystemParamValidationError` that skips the system. - /// The parameter name is initialized to the type name of `T`, so a `SystemParam` should usually pass `Self`. - pub fn skipped(message: impl Into>) -> Self { - Self::new::(true, message, Cow::Borrowed("")) - } - - /// Constructs a `SystemParamValidationError` for an invalid parameter that should be treated as an error. - /// The parameter name is initialized to the type name of `T`, so a `SystemParam` should usually pass `Self`. - pub fn invalid(message: impl Into>) -> Self { - Self::new::(false, message, Cow::Borrowed("")) - } - - /// Constructs a `SystemParamValidationError` for an invalid parameter. - /// The parameter name is initialized to the type name of `T`, so a `SystemParam` should usually pass `Self`. - pub fn new( - skipped: bool, - message: impl Into>, - field: impl Into>, - ) -> Self { - Self { - skipped, - message: message.into(), - param: DebugName::type_name::(), - field: field.into(), - } - } - - pub(crate) const EMPTY: Self = Self { - skipped: false, - message: Cow::Borrowed(""), - param: DebugName::borrowed(""), - field: Cow::Borrowed(""), - }; -} - -impl Display for SystemParamValidationError { - fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { - write!( - fmt, - "Parameter `{}{}` failed validation: {}", - self.param.shortname(), - self.field, - self.message - )?; - if !self.skipped { - write!(fmt, "\nIf this is an expected state, wrap the parameter in `Option` and handle `None` when it happens, or wrap the parameter in `When` to skip the system when it happens.")?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{event::Event, system::assert_is_system}; - use core::cell::RefCell; - - // Compile test for https://github.com/bevyengine/bevy/pull/2838. - #[test] - fn system_param_generic_bounds() { - #[derive(SystemParam)] - pub struct SpecialQuery< - 'w, - 's, - D: QueryData + Send + Sync + 'static, - F: QueryFilter + Send + Sync + 'static = (), - > { - _query: Query<'w, 's, D, F>, - } - - fn my_system(_: SpecialQuery<(), ()>) {} - assert_is_system(my_system); - } - - // Compile tests for https://github.com/bevyengine/bevy/pull/6694. - #[test] - fn system_param_flexibility() { - #[derive(SystemParam)] - pub struct SpecialRes<'w, T: Resource> { - _res: Res<'w, T>, - } - - #[derive(SystemParam)] - pub struct SpecialLocal<'s, T: FromWorld + Send + 'static> { - _local: Local<'s, T>, - } - - #[derive(Resource)] - struct R; - - fn my_system(_: SpecialRes, _: SpecialLocal) {} - assert_is_system(my_system); - } - - #[derive(Resource)] - pub struct R; - - // Compile test for https://github.com/bevyengine/bevy/pull/7001. - #[test] - fn system_param_const_generics() { - #[expect( - dead_code, - reason = "This struct is used to ensure that const generics are supported as a SystemParam; thus, the inner value never needs to be read." - )] - #[derive(SystemParam)] - pub struct ConstGenericParam<'w, const I: usize>(Res<'w, R>); - - fn my_system(_: ConstGenericParam<0>, _: ConstGenericParam<1000>) {} - assert_is_system(my_system); - } - - // Compile test for https://github.com/bevyengine/bevy/pull/6867. - #[test] - fn system_param_field_limit() { - #[derive(SystemParam)] - pub struct LongParam<'w> { - // Each field should be a distinct type so there will - // be an error if the derive messes up the field order. - _r0: Res<'w, R<0>>, - _r1: Res<'w, R<1>>, - _r2: Res<'w, R<2>>, - _r3: Res<'w, R<3>>, - _r4: Res<'w, R<4>>, - _r5: Res<'w, R<5>>, - _r6: Res<'w, R<6>>, - _r7: Res<'w, R<7>>, - _r8: Res<'w, R<8>>, - _r9: Res<'w, R<9>>, - _r10: Res<'w, R<10>>, - _r11: Res<'w, R<11>>, - _r12: Res<'w, R<12>>, - _r13: Res<'w, R<13>>, - _r14: Res<'w, R<14>>, - _r15: Res<'w, R<15>>, - _r16: Res<'w, R<16>>, - } - - fn long_system(_: LongParam) {} - assert_is_system(long_system); - } - - // Compile test for https://github.com/bevyengine/bevy/pull/6919. - // Regression test for https://github.com/bevyengine/bevy/issues/7447. - #[test] - fn system_param_phantom_data() { - #[derive(SystemParam)] - struct PhantomParam<'w, T: Resource, Marker: 'static> { - _foo: Res<'w, T>, - marker: PhantomData<&'w Marker>, - } - - fn my_system(_: PhantomParam, ()>) {} - assert_is_system(my_system); - } - - // Compile tests for https://github.com/bevyengine/bevy/pull/6957. - #[test] - fn system_param_struct_variants() { - #[derive(SystemParam)] - pub struct UnitParam; - - #[expect( - dead_code, - reason = "This struct is used to ensure that tuple structs are supported as a SystemParam; thus, the inner values never need to be read." - )] - #[derive(SystemParam)] - pub struct TupleParam<'w, 's, R: Resource, L: FromWorld + Send + 'static>( - Res<'w, R>, - Local<'s, L>, - ); - - fn my_system(_: UnitParam, _: TupleParam, u32>) {} - assert_is_system(my_system); - } - - // Regression test for https://github.com/bevyengine/bevy/issues/4200. - #[test] - fn system_param_private_fields() { - #[derive(Resource)] - struct PrivateResource; - - #[expect( - dead_code, - reason = "This struct is used to ensure that SystemParam's derive can't leak private fields; thus, the inner values never need to be read." - )] - #[derive(SystemParam)] - pub struct EncapsulatedParam<'w>(Res<'w, PrivateResource>); - - fn my_system(_: EncapsulatedParam) {} - assert_is_system(my_system); - } - - // Regression test for https://github.com/bevyengine/bevy/issues/7103. - #[test] - fn system_param_where_clause() { - #[derive(SystemParam)] - pub struct WhereParam<'w, 's, D> - where - D: 'static + QueryData, - { - _q: Query<'w, 's, D, ()>, - } - - fn my_system(_: WhereParam<()>) {} - assert_is_system(my_system); - } - - // Regression test for https://github.com/bevyengine/bevy/issues/1727. - #[test] - fn system_param_name_collision() { - #[derive(Resource)] - pub struct FetchState; - - #[derive(SystemParam)] - pub struct Collide<'w> { - _x: Res<'w, FetchState>, - } - - fn my_system(_: Collide) {} - assert_is_system(my_system); - } - - // Regression test for https://github.com/bevyengine/bevy/issues/8192. - #[test] - fn system_param_invariant_lifetime() { - #[derive(SystemParam)] - pub struct InvariantParam<'w, 's> { - _set: ParamSet<'w, 's, (Query<'w, 's, ()>,)>, - } - - fn my_system(_: InvariantParam) {} - assert_is_system(my_system); - } - - // Compile test for https://github.com/bevyengine/bevy/pull/9589. - #[test] - fn non_sync_local() { - fn non_sync_system(cell: Local>) { - assert_eq!(*cell.borrow(), 0); - } - - let mut world = World::new(); - let mut schedule = crate::schedule::Schedule::default(); - schedule.add_systems(non_sync_system); - schedule.run(&mut world); - } - - // Regression test for https://github.com/bevyengine/bevy/issues/10207. - #[test] - fn param_set_non_send_first() { - fn non_send_param_set(mut p: ParamSet<(NonSend<*mut u8>, ())>) { - let _ = p.p0(); - p.p1(); - } - - let mut world = World::new(); - world.insert_non_send_resource(core::ptr::null_mut::()); - let mut schedule = crate::schedule::Schedule::default(); - schedule.add_systems((non_send_param_set, non_send_param_set, non_send_param_set)); - schedule.run(&mut world); - } - - // Regression test for https://github.com/bevyengine/bevy/issues/10207. - #[test] - fn param_set_non_send_second() { - fn non_send_param_set(mut p: ParamSet<((), NonSendMut<*mut u8>)>) { - p.p0(); - let _ = p.p1(); - } - - let mut world = World::new(); - world.insert_non_send_resource(core::ptr::null_mut::()); - let mut schedule = crate::schedule::Schedule::default(); - schedule.add_systems((non_send_param_set, non_send_param_set, non_send_param_set)); - schedule.run(&mut world); - } - - fn _dyn_system_param_type_inference(mut p: DynSystemParam) { - // Make sure the downcast() methods are able to infer their type parameters from the use of the return type. - // This is just a compilation test, so there is nothing to run. - let _query: Query<()> = p.downcast_mut().unwrap(); - let _query: Query<()> = p.downcast_mut_inner().unwrap(); - let _query: Query<()> = p.downcast().unwrap(); - } - - #[test] - #[should_panic] - fn missing_resource_error() { - #[derive(Resource)] - pub struct MissingResource; - - let mut schedule = crate::schedule::Schedule::default(); - schedule.add_systems(res_system); - let mut world = World::new(); - schedule.run(&mut world); - - fn res_system(_: Res) {} - } - - #[test] - #[should_panic] - fn missing_event_error() { - use crate::prelude::{BufferedEvent, EventReader}; - - #[derive(Event, BufferedEvent)] - pub struct MissingEvent; - - let mut schedule = crate::schedule::Schedule::default(); - schedule.add_systems(event_system); - let mut world = World::new(); - schedule.run(&mut world); - - fn event_system(_: EventReader) {} - } -} diff --git a/src/system/system_registry.rs b/src/system/system_registry.rs deleted file mode 100644 index bc87cd4..0000000 --- a/src/system/system_registry.rs +++ /dev/null @@ -1,975 +0,0 @@ -#[cfg(feature = "bevy_reflect")] -use crate::reflect::ReflectComponent; -use crate::{ - change_detection::Mut, - entity::Entity, - error::BevyError, - system::{ - input::SystemInput, BoxedSystem, IntoSystem, RunSystemError, SystemParamValidationError, - }, - world::World, -}; -use alloc::boxed::Box; -use bevy_ecs_macros::{Component, Resource}; -#[cfg(feature = "bevy_reflect")] -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use core::marker::PhantomData; -use thiserror::Error; - -/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized. -#[derive(Component)] -#[require(SystemIdMarker)] -pub(crate) struct RegisteredSystem { - initialized: bool, - system: BoxedSystem, -} - -impl RegisteredSystem { - pub fn new(system: BoxedSystem) -> Self { - RegisteredSystem { - initialized: false, - system, - } - } -} - -/// Marker [`Component`](bevy_ecs::component::Component) for identifying [`SystemId`] [`Entity`]s. -#[derive(Component, Default)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Component, Default))] -pub struct SystemIdMarker; - -/// A system that has been removed from the registry. -/// It contains the system and whether or not it has been initialized. -/// -/// This struct is returned by [`World::unregister_system`]. -pub struct RemovedSystem { - initialized: bool, - system: BoxedSystem, -} - -impl RemovedSystem { - /// Is the system initialized? - /// A system is initialized the first time it's ran. - pub fn initialized(&self) -> bool { - self.initialized - } - - /// The system removed from the storage. - pub fn system(self) -> BoxedSystem { - self.system - } -} - -/// An identifier for a registered system. -/// -/// These are opaque identifiers, keyed to a specific [`World`], -/// and are created via [`World::register_system`]. -pub struct SystemId { - pub(crate) entity: Entity, - pub(crate) marker: PhantomData O>, -} - -impl SystemId { - /// Transforms a [`SystemId`] into the [`Entity`] that holds the one-shot system's state. - /// - /// It's trivial to convert [`SystemId`] into an [`Entity`] since a one-shot system - /// is really an entity with associated handler function. - /// - /// For example, this is useful if you want to assign a name label to a system. - pub fn entity(self) -> Entity { - self.entity - } - - /// Create [`SystemId`] from an [`Entity`]. Useful when you only have entity handles to avoid - /// adding extra components that have a [`SystemId`] everywhere. To run a system with this ID - /// - The entity must be a system - /// - The `I` + `O` types must be correct - pub fn from_entity(entity: Entity) -> Self { - Self { - entity, - marker: PhantomData, - } - } -} - -impl Eq for SystemId {} - -// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters. -impl Copy for SystemId {} - -// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters. -impl Clone for SystemId { - fn clone(&self) -> Self { - *self - } -} - -// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters. -impl PartialEq for SystemId { - fn eq(&self, other: &Self) -> bool { - self.entity == other.entity && self.marker == other.marker - } -} - -// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters. -impl core::hash::Hash for SystemId { - fn hash(&self, state: &mut H) { - self.entity.hash(state); - } -} - -impl core::fmt::Debug for SystemId { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("SystemId").field(&self.entity).finish() - } -} - -/// A cached [`SystemId`] distinguished by the unique function type of its system. -/// -/// This resource is inserted by [`World::register_system_cached`]. -#[derive(Resource)] -pub struct CachedSystemId { - /// The cached `SystemId` as an `Entity`. - pub entity: Entity, - _marker: PhantomData S>, -} - -impl CachedSystemId { - /// Creates a new `CachedSystemId` struct given a `SystemId`. - pub fn new(id: SystemId) -> Self { - Self { - entity: id.entity(), - _marker: PhantomData, - } - } -} - -impl World { - /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. - /// - /// It's possible to register multiple copies of the same system by calling this function - /// multiple times. If that's not what you want, consider using [`World::register_system_cached`] - /// instead. - /// - /// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule), - /// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system. - /// This allows for running systems in a pushed-based fashion. - /// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases - /// due to its better performance and ability to run non-conflicting systems simultaneously. - pub fn register_system( - &mut self, - system: impl IntoSystem + 'static, - ) -> SystemId - where - I: SystemInput + 'static, - O: 'static, - { - self.register_boxed_system(Box::new(IntoSystem::into_system(system))) - } - - /// Similar to [`Self::register_system`], but allows passing in a [`BoxedSystem`]. - /// - /// This is useful if the [`IntoSystem`] implementor has already been turned into a - /// [`System`](crate::system::System) trait object and put in a [`Box`]. - pub fn register_boxed_system(&mut self, system: BoxedSystem) -> SystemId - where - I: SystemInput + 'static, - O: 'static, - { - let entity = self.spawn(RegisteredSystem::new(system)).id(); - SystemId::from_entity(entity) - } - - /// Removes a registered system and returns the system, if it exists. - /// After removing a system, the [`SystemId`] becomes invalid and attempting to use it afterwards will result in errors. - /// Re-adding the removed system will register it on a new [`SystemId`]. - /// - /// If no system corresponds to the given [`SystemId`], this method returns an error. - /// Systems are also not allowed to remove themselves, this returns an error too. - pub fn unregister_system( - &mut self, - id: SystemId, - ) -> Result, RegisteredSystemError> - where - I: SystemInput + 'static, - O: 'static, - { - match self.get_entity_mut(id.entity) { - Ok(mut entity) => { - let registered_system = entity - .take::>() - .ok_or(RegisteredSystemError::SelfRemove(id))?; - entity.despawn(); - Ok(RemovedSystem { - initialized: registered_system.initialized, - system: registered_system.system, - }) - } - Err(_) => Err(RegisteredSystemError::SystemIdNotRegistered(id)), - } - } - - /// Run stored systems by their [`SystemId`]. - /// Before running a system, it must first be registered. - /// The method [`World::register_system`] stores a given system and returns a [`SystemId`]. - /// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once), - /// because it keeps local state between calls and change detection works correctly. - /// - /// Also runs any queued-up commands. - /// - /// In order to run a chained system with an input, use [`World::run_system_with`] instead. - /// - /// # Examples - /// - /// ## Running a system - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// fn increment(mut counter: Local) { - /// *counter += 1; - /// println!("{}", *counter); - /// } - /// - /// let mut world = World::default(); - /// let counter_one = world.register_system(increment); - /// let counter_two = world.register_system(increment); - /// world.run_system(counter_one); // -> 1 - /// world.run_system(counter_one); // -> 2 - /// world.run_system(counter_two); // -> 1 - /// ``` - /// - /// ## Change detection - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// #[derive(Resource, Default)] - /// struct ChangeDetector; - /// - /// let mut world = World::default(); - /// world.init_resource::(); - /// let detector = world.register_system(|change_detector: ResMut| { - /// if change_detector.is_changed() { - /// println!("Something happened!"); - /// } else { - /// println!("Nothing happened."); - /// } - /// }); - /// - /// // Resources are changed when they are first added - /// let _ = world.run_system(detector); // -> Something happened! - /// let _ = world.run_system(detector); // -> Nothing happened. - /// world.resource_mut::().set_changed(); - /// let _ = world.run_system(detector); // -> Something happened! - /// ``` - /// - /// ## Getting system output - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// - /// #[derive(Resource)] - /// struct PlayerScore(i32); - /// - /// #[derive(Resource)] - /// struct OpponentScore(i32); - /// - /// fn get_player_score(player_score: Res) -> i32 { - /// player_score.0 - /// } - /// - /// fn get_opponent_score(opponent_score: Res) -> i32 { - /// opponent_score.0 - /// } - /// - /// let mut world = World::default(); - /// world.insert_resource(PlayerScore(3)); - /// world.insert_resource(OpponentScore(2)); - /// - /// let scoring_systems = [ - /// ("player", world.register_system(get_player_score)), - /// ("opponent", world.register_system(get_opponent_score)), - /// ]; - /// - /// for (label, scoring_system) in scoring_systems { - /// println!("{label} has score {}", world.run_system(scoring_system).expect("system succeeded")); - /// } - /// ``` - pub fn run_system( - &mut self, - id: SystemId<(), O>, - ) -> Result> { - self.run_system_with(id, ()) - } - - /// Run a stored chained system by its [`SystemId`], providing an input value. - /// Before running a system, it must first be registered. - /// The method [`World::register_system`] stores a given system and returns a [`SystemId`]. - /// - /// Also runs any queued-up commands. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// fn increment(In(increment_by): In, mut counter: Local) -> u8 { - /// *counter += increment_by; - /// *counter - /// } - /// - /// let mut world = World::default(); - /// let counter_one = world.register_system(increment); - /// let counter_two = world.register_system(increment); - /// assert_eq!(world.run_system_with(counter_one, 1).unwrap(), 1); - /// assert_eq!(world.run_system_with(counter_one, 20).unwrap(), 21); - /// assert_eq!(world.run_system_with(counter_two, 30).unwrap(), 30); - /// ``` - /// - /// See [`World::run_system`] for more examples. - pub fn run_system_with( - &mut self, - id: SystemId, - input: I::Inner<'_>, - ) -> Result> - where - I: SystemInput + 'static, - O: 'static, - { - // Lookup - let mut entity = self - .get_entity_mut(id.entity) - .map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?; - - // Take ownership of system trait object - let RegisteredSystem { - mut initialized, - mut system, - } = entity - .take::>() - .ok_or(RegisteredSystemError::Recursive(id))?; - - // Run the system - if !initialized { - system.initialize(self); - initialized = true; - } - - // Wait to run the commands until the system is available again. - // This is needed so the systems can recursively run themselves. - let result = system.run_without_applying_deferred(input, self); - system.queue_deferred(self.into()); - - // Return ownership of system trait object (if entity still exists) - if let Ok(mut entity) = self.get_entity_mut(id.entity) { - entity.insert::>(RegisteredSystem { - initialized, - system, - }); - } - - // Run any commands enqueued by the system - self.flush(); - Ok(result?) - } - - /// Registers a system or returns its cached [`SystemId`]. - /// - /// If you want to run the system immediately and you don't need its `SystemId`, see - /// [`World::run_system_cached`]. - /// - /// The first time this function is called for a particular system, it will register it and - /// store its [`SystemId`] in a [`CachedSystemId`] resource for later. If you would rather - /// manage the `SystemId` yourself, or register multiple copies of the same system, use - /// [`World::register_system`] instead. - /// - /// # Limitations - /// - /// This function only accepts ZST (zero-sized) systems to guarantee that any two systems of - /// the same type must be equal. This means that closures that capture the environment, and - /// function pointers, are not accepted. - /// - /// If you want to access values from the environment within a system, consider passing them in - /// as inputs via [`World::run_system_cached_with`]. If that's not an option, consider - /// [`World::register_system`] instead. - pub fn register_system_cached(&mut self, system: S) -> SystemId - where - I: SystemInput + 'static, - O: 'static, - S: IntoSystem + 'static, - { - const { - assert!( - size_of::() == 0, - "Non-ZST systems (e.g. capturing closures, function pointers) cannot be cached.", - ); - } - - if !self.contains_resource::>() { - let id = self.register_system(system); - self.insert_resource(CachedSystemId::::new(id)); - return id; - } - - self.resource_scope(|world, mut id: Mut>| { - if let Ok(mut entity) = world.get_entity_mut(id.entity) { - if !entity.contains::>() { - entity.insert(RegisteredSystem::new(Box::new(IntoSystem::into_system( - system, - )))); - } - } else { - id.entity = world.register_system(system).entity(); - } - SystemId::from_entity(id.entity) - }) - } - - /// Removes a cached system and its [`CachedSystemId`] resource. - /// - /// See [`World::register_system_cached`] for more information. - pub fn unregister_system_cached( - &mut self, - _system: S, - ) -> Result, RegisteredSystemError> - where - I: SystemInput + 'static, - O: 'static, - S: IntoSystem + 'static, - { - let id = self - .remove_resource::>() - .ok_or(RegisteredSystemError::SystemNotCached)?; - self.unregister_system(SystemId::::from_entity(id.entity)) - } - - /// Runs a cached system, registering it if necessary. - /// - /// See [`World::register_system_cached`] for more information. - pub fn run_system_cached + 'static>( - &mut self, - system: S, - ) -> Result> { - self.run_system_cached_with(system, ()) - } - - /// Runs a cached system with an input, registering it if necessary. - /// - /// See [`World::register_system_cached`] for more information. - pub fn run_system_cached_with( - &mut self, - system: S, - input: I::Inner<'_>, - ) -> Result> - where - I: SystemInput + 'static, - O: 'static, - S: IntoSystem + 'static, - { - let id = self.register_system_cached(system); - self.run_system_with(id, input) - } -} - -/// An operation with stored systems failed. -#[derive(Error)] -pub enum RegisteredSystemError { - /// A system was run by id, but no system with that id was found. - /// - /// Did you forget to register it? - #[error("System {0:?} was not registered")] - SystemIdNotRegistered(SystemId), - /// A cached system was removed by value, but no system with its type was found. - /// - /// Did you forget to register it? - #[error("Cached system was not found")] - SystemNotCached, - /// A system tried to run itself recursively. - #[error("System {0:?} tried to run itself recursively")] - Recursive(SystemId), - /// A system tried to remove itself. - #[error("System {0:?} tried to remove itself")] - SelfRemove(SystemId), - /// System could not be run due to parameters that failed validation. - /// This is not considered an error. - #[error("System did not run due to failed parameter validation: {0}")] - Skipped(SystemParamValidationError), - /// System returned an error or failed required parameter validation. - #[error("System returned error: {0}")] - Failed(BevyError), -} - -impl From for RegisteredSystemError { - fn from(value: RunSystemError) -> Self { - match value { - RunSystemError::Skipped(err) => Self::Skipped(err), - RunSystemError::Failed(err) => Self::Failed(err), - } - } -} - -impl core::fmt::Debug for RegisteredSystemError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::SystemIdNotRegistered(arg0) => { - f.debug_tuple("SystemIdNotRegistered").field(arg0).finish() - } - Self::SystemNotCached => write!(f, "SystemNotCached"), - Self::Recursive(arg0) => f.debug_tuple("Recursive").field(arg0).finish(), - Self::SelfRemove(arg0) => f.debug_tuple("SelfRemove").field(arg0).finish(), - Self::Skipped(arg0) => f.debug_tuple("Skipped").field(arg0).finish(), - Self::Failed(arg0) => f.debug_tuple("Failed").field(arg0).finish(), - } - } -} - -#[cfg(test)] -mod tests { - use core::cell::Cell; - - use bevy_utils::default; - - use crate::{prelude::*, system::SystemId}; - - #[derive(Resource, Default, PartialEq, Debug)] - struct Counter(u8); - - #[test] - fn change_detection() { - #[derive(Resource, Default)] - struct ChangeDetector; - - fn count_up_iff_changed( - mut counter: ResMut, - change_detector: ResMut, - ) { - if change_detector.is_changed() { - counter.0 += 1; - } - } - - let mut world = World::new(); - world.init_resource::(); - world.init_resource::(); - assert_eq!(*world.resource::(), Counter(0)); - // Resources are changed when they are first added. - let id = world.register_system(count_up_iff_changed); - world.run_system(id).expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(1)); - // Nothing changed - world.run_system(id).expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(1)); - // Making a change - world.resource_mut::().set_changed(); - world.run_system(id).expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(2)); - } - - #[test] - fn local_variables() { - // The `Local` begins at the default value of 0 - fn doubling(last_counter: Local, mut counter: ResMut) { - counter.0 += last_counter.0 .0; - last_counter.0 .0 = counter.0; - } - - let mut world = World::new(); - world.insert_resource(Counter(1)); - assert_eq!(*world.resource::(), Counter(1)); - let id = world.register_system(doubling); - world.run_system(id).expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(1)); - world.run_system(id).expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(2)); - world.run_system(id).expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(4)); - world.run_system(id).expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(8)); - } - - #[test] - fn input_values() { - // Verify that a non-Copy, non-Clone type can be passed in. - struct NonCopy(u8); - - fn increment_sys(In(NonCopy(increment_by)): In, mut counter: ResMut) { - counter.0 += increment_by; - } - - let mut world = World::new(); - - let id = world.register_system(increment_sys); - - // Insert the resource after registering the system. - world.insert_resource(Counter(1)); - assert_eq!(*world.resource::(), Counter(1)); - - world - .run_system_with(id, NonCopy(1)) - .expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(2)); - - world - .run_system_with(id, NonCopy(1)) - .expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(3)); - - world - .run_system_with(id, NonCopy(20)) - .expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(23)); - - world - .run_system_with(id, NonCopy(1)) - .expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(24)); - } - - #[test] - fn output_values() { - // Verify that a non-Copy, non-Clone type can be returned. - #[derive(Eq, PartialEq, Debug)] - struct NonCopy(u8); - - fn increment_sys(mut counter: ResMut) -> NonCopy { - counter.0 += 1; - NonCopy(counter.0) - } - - let mut world = World::new(); - - let id = world.register_system(increment_sys); - - // Insert the resource after registering the system. - world.insert_resource(Counter(1)); - assert_eq!(*world.resource::(), Counter(1)); - - let output = world.run_system(id).expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(2)); - assert_eq!(output, NonCopy(2)); - - let output = world.run_system(id).expect("system runs successfully"); - assert_eq!(*world.resource::(), Counter(3)); - assert_eq!(output, NonCopy(3)); - } - - #[test] - fn fallible_system() { - fn sys() -> Result<()> { - Err("error")?; - Ok(()) - } - - let mut world = World::new(); - let fallible_system_id = world.register_system(sys); - let output = world.run_system(fallible_system_id); - assert!(matches!(output, Ok(Err(_)))); - } - - #[test] - fn exclusive_system() { - let mut world = World::new(); - let exclusive_system_id = world.register_system(|world: &mut World| { - world.spawn_empty(); - }); - let entity_count = world.entities.len(); - let _ = world.run_system(exclusive_system_id); - assert_eq!(world.entities.len(), entity_count + 1); - } - - #[test] - fn nested_systems() { - use crate::system::SystemId; - - #[derive(Component)] - struct Callback(SystemId); - - fn nested(query: Query<&Callback>, mut commands: Commands) { - for callback in query.iter() { - commands.run_system(callback.0); - } - } - - let mut world = World::new(); - world.insert_resource(Counter(0)); - - let increment_two = world.register_system(|mut counter: ResMut| { - counter.0 += 2; - }); - let increment_three = world.register_system(|mut counter: ResMut| { - counter.0 += 3; - }); - let nested_id = world.register_system(nested); - - world.spawn(Callback(increment_two)); - world.spawn(Callback(increment_three)); - let _ = world.run_system(nested_id); - assert_eq!(*world.resource::(), Counter(5)); - } - - #[test] - fn nested_systems_with_inputs() { - use crate::system::SystemId; - - #[derive(Component)] - struct Callback(SystemId>, u8); - - fn nested(query: Query<&Callback>, mut commands: Commands) { - for callback in query.iter() { - commands.run_system_with(callback.0, callback.1); - } - } - - let mut world = World::new(); - world.insert_resource(Counter(0)); - - let increment_by = - world.register_system(|In(amt): In, mut counter: ResMut| { - counter.0 += amt; - }); - let nested_id = world.register_system(nested); - - world.spawn(Callback(increment_by, 2)); - world.spawn(Callback(increment_by, 3)); - let _ = world.run_system(nested_id); - assert_eq!(*world.resource::(), Counter(5)); - } - - #[test] - fn cached_system() { - use crate::system::RegisteredSystemError; - - fn four() -> i32 { - 4 - } - - let mut world = World::new(); - let old = world.register_system_cached(four); - let new = world.register_system_cached(four); - assert_eq!(old, new); - - let result = world.unregister_system_cached(four); - assert!(result.is_ok()); - let new = world.register_system_cached(four); - assert_ne!(old, new); - - let output = world.run_system(old); - assert!(matches!( - output, - Err(RegisteredSystemError::SystemIdNotRegistered(x)) if x == old, - )); - let output = world.run_system(new); - assert!(matches!(output, Ok(x) if x == four())); - let output = world.run_system_cached(four); - assert!(matches!(output, Ok(x) if x == four())); - let output = world.run_system_cached_with(four, ()); - assert!(matches!(output, Ok(x) if x == four())); - } - - #[test] - fn cached_fallible_system() { - fn sys() -> Result<()> { - Err("error")?; - Ok(()) - } - - let mut world = World::new(); - let fallible_system_id = world.register_system_cached(sys); - let output = world.run_system(fallible_system_id); - assert!(matches!(output, Ok(Err(_)))); - let output = world.run_system_cached(sys); - assert!(matches!(output, Ok(Err(_)))); - let output = world.run_system_cached_with(sys, ()); - assert!(matches!(output, Ok(Err(_)))); - } - - #[test] - fn cached_system_commands() { - fn sys(mut counter: ResMut) { - counter.0 += 1; - } - - let mut world = World::new(); - world.insert_resource(Counter(0)); - world.commands().run_system_cached(sys); - world.flush_commands(); - assert_eq!(world.resource::().0, 1); - world.commands().run_system_cached_with(sys, ()); - world.flush_commands(); - assert_eq!(world.resource::().0, 2); - } - - #[test] - fn cached_fallible_system_commands() { - fn sys(mut counter: ResMut) -> Result { - counter.0 += 1; - Ok(()) - } - - let mut world = World::new(); - world.insert_resource(Counter(0)); - world.commands().run_system_cached(sys); - world.flush_commands(); - assert_eq!(world.resource::().0, 1); - world.commands().run_system_cached_with(sys, ()); - world.flush_commands(); - assert_eq!(world.resource::().0, 2); - } - - #[test] - #[should_panic(expected = "This system always fails")] - fn cached_fallible_system_commands_can_fail() { - use crate::system::command; - fn sys() -> Result { - Err("This system always fails".into()) - } - - let mut world = World::new(); - world.commands().queue(command::run_system_cached(sys)); - world.flush_commands(); - } - - #[test] - fn cached_system_adapters() { - fn four() -> i32 { - 4 - } - - fn double(In(i): In) -> i32 { - i * 2 - } - - let mut world = World::new(); - - let output = world.run_system_cached(four.pipe(double)); - assert!(matches!(output, Ok(8))); - - let output = world.run_system_cached(four.map(|i| i * 2)); - assert!(matches!(output, Ok(8))); - } - - #[test] - fn cached_system_into_same_system_type() { - struct Foo; - impl IntoSystem<(), (), ()> for Foo { - type System = ApplyDeferred; - fn into_system(_: Self) -> Self::System { - ApplyDeferred - } - } - - struct Bar; - impl IntoSystem<(), (), ()> for Bar { - type System = ApplyDeferred; - fn into_system(_: Self) -> Self::System { - ApplyDeferred - } - } - - let mut world = World::new(); - let foo1 = world.register_system_cached(Foo); - let foo2 = world.register_system_cached(Foo); - let bar1 = world.register_system_cached(Bar); - let bar2 = world.register_system_cached(Bar); - - // The `S: IntoSystem` types are different, so they should be cached - // as separate systems, even though the `::System` - // types / values are the same (`ApplyDeferred`). - assert_ne!(foo1, bar1); - - // But if the `S: IntoSystem` types are the same, they'll be cached - // as the same system. - assert_eq!(foo1, foo2); - assert_eq!(bar1, bar2); - } - - #[test] - fn system_with_input_ref() { - fn with_ref(InRef(input): InRef, mut counter: ResMut) { - counter.0 += *input; - } - - let mut world = World::new(); - world.insert_resource(Counter(0)); - - let id = world.register_system(with_ref); - world.run_system_with(id, &2).unwrap(); - assert_eq!(*world.resource::(), Counter(2)); - } - - #[test] - fn system_with_input_mut() { - #[derive(Event)] - struct MyEvent { - cancelled: bool, - } - - fn post(InMut(event): InMut, counter: ResMut) { - if counter.0 > 0 { - event.cancelled = true; - } - } - - let mut world = World::new(); - world.insert_resource(Counter(0)); - let post_system = world.register_system(post); - - let mut event = MyEvent { cancelled: false }; - world.run_system_with(post_system, &mut event).unwrap(); - assert!(!event.cancelled); - - world.resource_mut::().0 = 1; - world.run_system_with(post_system, &mut event).unwrap(); - assert!(event.cancelled); - } - - #[test] - fn run_system_invalid_params() { - use crate::system::RegisteredSystemError; - use alloc::string::ToString; - - struct T; - impl Resource for T {} - fn system(_: Res) {} - - let mut world = World::new(); - let id = world.register_system(system); - // This fails because `T` has not been added to the world yet. - let result = world.run_system(id); - - assert!(matches!(result, Err(RegisteredSystemError::Failed { .. }))); - let expected = "System returned error: Parameter `Res` failed validation: Resource does not exist\n"; - assert!(result.unwrap_err().to_string().contains(expected)); - } - - #[test] - fn run_system_recursive() { - std::thread_local! { - static INVOCATIONS_LEFT: Cell = const { Cell::new(3) }; - static SYSTEM_ID: Cell> = default(); - } - - fn system(mut commands: Commands) { - let count = INVOCATIONS_LEFT.get() - 1; - INVOCATIONS_LEFT.set(count); - if count > 0 { - commands.run_system(SYSTEM_ID.get().unwrap()); - } - } - - let mut world = World::new(); - let id = world.register_system(system); - SYSTEM_ID.set(Some(id)); - world.run_system(id).unwrap(); - - assert_eq!(INVOCATIONS_LEFT.get(), 0); - } - - #[test] - fn run_system_exclusive_adapters() { - let mut world = World::new(); - fn system(_: &mut World) {} - world.run_system_cached(system).unwrap(); - world.run_system_cached(system.pipe(system)).unwrap(); - world.run_system_cached(system.map(|()| {})).unwrap(); - } -} diff --git a/src/systems.rs b/src/systems.rs deleted file mode 100644 index 29e1f8a..0000000 --- a/src/systems.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::components::Position; - -pub struct MovementSystem; - -impl MovementSystem { - pub fn update(pos: &Position, dx: i32, dy: i32) -> Position { - Position { - x: (pos.x as i32 + dx).max(0) as u32, - y: (pos.y as i32 + dy).max(0) as u32, - } - } -} diff --git a/src/world.rs b/src/world.rs index 5d8407d..ab61363 100644 --- a/src/world.rs +++ b/src/world.rs @@ -9,15 +9,15 @@ use soroban_sdk::{Symbol, Vec}; #[derive(Debug, Clone)] pub struct World { /// Entity manager for handling entity lifecycle - pub entities: EntityManager, + entities: EntityManager, /// Component registry for managing component types - pub components: ComponentRegistry, + components: ComponentRegistry, /// Component storage system - pub storage: Storage, + storage: Storage, /// Resources (global state) - pub resources: Vec, + resources: Vec, /// Event system - pub events: Vec, + events: Vec, } impl World { @@ -227,6 +227,31 @@ impl World { self.entities.iter_entities_mut() } + /// Access the entity manager. + pub fn entity_manager(&self) -> &EntityManager { + &self.entities + } + + /// Access the registered component metadata. + pub fn component_registry(&self) -> &ComponentRegistry { + &self.components + } + + /// Access component storage. + pub fn storage(&self) -> &Storage { + &self.storage + } + + /// Access world resources. + pub fn resources(&self) -> &Vec { + &self.resources + } + + /// Access queued world events. + pub fn events(&self) -> &Vec { + &self.events + } + /// Query entities with specific components pub fn query_entities(&self, component_types: &[Symbol]) -> Vec { let env = soroban_sdk::Env::default(); diff --git a/src/zk/advanced.rs b/src/zk/advanced.rs new file mode 100644 index 0000000..7febc2e --- /dev/null +++ b/src/zk/advanced.rs @@ -0,0 +1,542 @@ +//! Experimental phase-3 ZK patterns. +//! +//! These APIs make the phase-3 roadmap concrete without overstating maturity: +//! +//! - fog-of-war orchestration around Merkle roots +//! - multiplayer state-channel transition contracts +//! - recursive proof-composition descriptors +//! +//! They remain part of `zk::experimental` because the repository is only +//! committing to explicit orchestration and public-input contracts here, not to +//! production-ready confidentiality guarantees. + +use alloc::vec::Vec; +use soroban_sdk::{contracttype, BytesN, Env}; + +use super::error::ZKError; +use super::merkle::tree::MerkleTree; +use super::traits::{bytes32_to_scalar, i32_to_scalar, u32_to_scalar, u64_to_scalar, GameCircuit}; +use super::types::{Groth16Proof, Scalar, VerificationKey}; + +/// Snapshot of a player's currently visible fog-of-war state. +#[contracttype] +#[derive(Clone, Debug)] +pub struct FogOfWarSnapshot { + /// Merkle root of the hidden map or board state. + pub map_root: BytesN<32>, + /// Merkle root of the tiles the player has already explored. + pub explored_root: BytesN<32>, + /// Player origin used by the exploration circuit. + pub origin_x: i32, + pub origin_y: i32, + /// Maximum Euclidean distance the player may reveal from the origin. + pub visibility_radius: u32, +} + +impl FogOfWarSnapshot { + /// Returns `true` when the target tile is within the visible window. + pub fn can_reveal(&self, tile_x: i32, tile_y: i32) -> bool { + let dx = i64::from(tile_x) - i64::from(self.origin_x); + let dy = i64::from(tile_y) - i64::from(self.origin_y); + let radius = i64::from(self.visibility_radius); + + dx * dx + dy * dy <= radius * radius + } +} + +/// Root transition for a single fog-of-war exploration update. +#[contracttype] +#[derive(Clone, Debug)] +pub struct FogOfWarTransition { + pub prior_explored_root: BytesN<32>, + pub next_explored_root: BytesN<32>, + pub tile_x: i32, + pub tile_y: i32, +} + +/// Apply a validated exploration update to a fog-of-war snapshot. +pub fn apply_fog_of_war_transition( + snapshot: &FogOfWarSnapshot, + transition: &FogOfWarTransition, +) -> Result { + if snapshot.explored_root != transition.prior_explored_root { + return Err(ZKError::InvalidStateTransition); + } + + if !snapshot.can_reveal(transition.tile_x, transition.tile_y) { + return Err(ZKError::InvalidVisibility); + } + + let mut updated = snapshot.clone(); + updated.explored_root = transition.next_explored_root.clone(); + Ok(updated) +} + +/// Experimental circuit contract for fog-of-war exploration proofs. +pub struct FogOfWarCircuit { + pub vk: VerificationKey, + pub max_visibility_radius: u32, +} + +impl GameCircuit for FogOfWarCircuit { + fn verification_key(&self) -> &VerificationKey { + &self.vk + } +} + +impl FogOfWarCircuit { + pub fn new(vk: VerificationKey, max_visibility_radius: u32) -> Self { + Self { + vk, + max_visibility_radius, + } + } + + /// Verify that a fog-of-war transition is valid for the provided snapshot. + /// + /// Public inputs: + /// `[map_root, prior_explored_root, next_explored_root, origin_x, origin_y, tile_x, tile_y, visibility_radius]`. + pub fn verify_exploration( + &self, + env: &Env, + proof: &Groth16Proof, + snapshot: &FogOfWarSnapshot, + transition: &FogOfWarTransition, + ) -> Result { + if snapshot.visibility_radius > self.max_visibility_radius { + return Err(ZKError::InvalidVisibility); + } + + let _ = apply_fog_of_war_transition(snapshot, transition)?; + + let public_inputs = Vec::from([ + bytes32_to_scalar(&snapshot.map_root), + bytes32_to_scalar(&transition.prior_explored_root), + bytes32_to_scalar(&transition.next_explored_root), + i32_to_scalar(env, snapshot.origin_x), + i32_to_scalar(env, snapshot.origin_y), + i32_to_scalar(env, transition.tile_x), + i32_to_scalar(env, transition.tile_y), + u32_to_scalar(env, snapshot.visibility_radius), + ]); + + self.verify_with_inputs(env, proof, &public_inputs) + } +} + +/// Off-chain state channel tracked by on-chain commitments and dispute metadata. +#[contracttype] +#[derive(Clone, Debug)] +pub struct ZkStateChannel { + pub channel_id: BytesN<32>, + pub participants_root: BytesN<32>, + pub state_root: BytesN<32>, + pub round: u64, + pub dispute_deadline: u64, + pub closed: bool, +} + +/// Proposed state transition for a multiplayer ZK state channel. +#[contracttype] +#[derive(Clone, Debug)] +pub struct StateChannelTransition { + pub prior_state_root: BytesN<32>, + pub next_state_root: BytesN<32>, + pub round: u64, + pub submitted_at: u64, +} + +/// Open a new experimental ZK state channel. +pub fn open_state_channel( + channel_id: BytesN<32>, + participants_root: BytesN<32>, + initial_state_root: BytesN<32>, + dispute_deadline: u64, +) -> Result { + if dispute_deadline == 0 { + return Err(ZKError::InvalidInput); + } + + Ok(ZkStateChannel { + channel_id, + participants_root, + state_root: initial_state_root, + round: 0, + dispute_deadline, + closed: false, + }) +} + +/// Apply a verified transition to the state channel. +pub fn apply_state_channel_transition( + channel: &ZkStateChannel, + transition: &StateChannelTransition, +) -> Result { + if channel.closed { + return Err(ZKError::ChannelClosed); + } + + if transition.prior_state_root != channel.state_root { + return Err(ZKError::InvalidStateTransition); + } + + let expected_round = channel + .round + .checked_add(1) + .ok_or(ZKError::InvalidStateTransition)?; + if transition.round != expected_round { + return Err(ZKError::InvalidStateTransition); + } + + if transition.submitted_at > channel.dispute_deadline { + return Err(ZKError::DeadlineExpired); + } + + let mut updated = channel.clone(); + updated.state_root = transition.next_state_root.clone(); + updated.round = transition.round; + Ok(updated) +} + +/// Close a channel with the latest accepted state root. +pub fn close_state_channel( + channel: &ZkStateChannel, + final_state_root: &BytesN<32>, + final_round: u64, + closed_at: u64, +) -> Result { + if channel.closed { + return Err(ZKError::ChannelClosed); + } + + if final_round < channel.round { + return Err(ZKError::InvalidStateTransition); + } + + let mut closed = channel.clone(); + closed.state_root = final_state_root.clone(); + closed.round = final_round; + closed.dispute_deadline = closed_at; + closed.closed = true; + Ok(closed) +} + +/// Experimental circuit contract for channel transition proofs. +pub struct StateChannelCircuit { + pub vk: VerificationKey, +} + +impl GameCircuit for StateChannelCircuit { + fn verification_key(&self) -> &VerificationKey { + &self.vk + } +} + +impl StateChannelCircuit { + pub fn new(vk: VerificationKey) -> Self { + Self { vk } + } + + /// Verify a state transition for a channel. + /// + /// Public inputs: + /// `[channel_id, participants_root, prior_state_root, next_state_root, round, submitted_at]`. + pub fn verify_transition( + &self, + env: &Env, + proof: &Groth16Proof, + channel: &ZkStateChannel, + transition: &StateChannelTransition, + ) -> Result { + if channel.closed { + return Err(ZKError::ChannelClosed); + } + + let _ = apply_state_channel_transition(channel, transition)?; + let public_inputs = Vec::from([ + bytes32_to_scalar(&channel.channel_id), + bytes32_to_scalar(&channel.participants_root), + bytes32_to_scalar(&transition.prior_state_root), + bytes32_to_scalar(&transition.next_state_root), + u64_to_scalar(env, transition.round), + u64_to_scalar(env, transition.submitted_at), + ]); + + self.verify_with_inputs(env, proof, &public_inputs) + } +} + +/// Experimental descriptor for a recursive proof batch. +#[contracttype] +#[derive(Clone, Debug)] +pub struct RecursiveProofLayout { + pub initial_state_root: BytesN<32>, + pub final_state_root: BytesN<32>, + pub accumulator_root: BytesN<32>, + pub proof_count: u32, +} + +impl RecursiveProofLayout { + /// Build a layout by folding per-step statement roots into a Merkle accumulator. + pub fn from_step_roots( + env: &Env, + initial_state_root: BytesN<32>, + final_state_root: BytesN<32>, + step_roots: &[BytesN<32>], + ) -> Result { + let accumulator_root = compose_statement_roots(env, step_roots)?; + Ok(Self { + initial_state_root, + final_state_root, + accumulator_root, + proof_count: step_roots.len() as u32, + }) + } +} + +/// Fold per-step statement roots into a deterministic Merkle accumulator root. +pub fn compose_statement_roots( + env: &Env, + step_roots: &[BytesN<32>], +) -> Result, ZKError> { + if step_roots.is_empty() { + return Err(ZKError::InvalidProofComposition); + } + + let mut leaves = Vec::with_capacity(step_roots.len()); + for root in step_roots { + leaves.push(root.to_array()); + } + + let tree = MerkleTree::from_leaves(env, &leaves)?; + Ok(tree.root_bytes(env)) +} + +/// Experimental circuit contract for recursive proof aggregation. +pub struct RecursiveProofCircuit { + pub vk: VerificationKey, + pub max_proof_count: u32, +} + +impl GameCircuit for RecursiveProofCircuit { + fn verification_key(&self) -> &VerificationKey { + &self.vk + } +} + +impl RecursiveProofCircuit { + pub fn new(vk: VerificationKey, max_proof_count: u32) -> Self { + Self { + vk, + max_proof_count, + } + } + + /// Verify an aggregated recursive-proof layout. + /// + /// Public inputs: + /// `[initial_state_root, final_state_root, accumulator_root, proof_count]`. + pub fn verify_composition( + &self, + env: &Env, + proof: &Groth16Proof, + layout: &RecursiveProofLayout, + ) -> Result { + if layout.proof_count == 0 || layout.proof_count > self.max_proof_count { + return Err(ZKError::InvalidProofComposition); + } + + let public_inputs: [Scalar; 4] = [ + bytes32_to_scalar(&layout.initial_state_root), + bytes32_to_scalar(&layout.final_state_root), + bytes32_to_scalar(&layout.accumulator_root), + u32_to_scalar(env, layout.proof_count), + ]; + + self.verify_with_inputs(env, proof, &public_inputs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::zk::types::{G1Point, G2Point}; + + fn make_vk(env: &Env, ic_count: u32) -> VerificationKey { + let g1 = G1Point { + bytes: BytesN::from_array(env, &[0u8; 64]), + }; + let g2 = G2Point { + bytes: BytesN::from_array(env, &[0u8; 128]), + }; + let mut ic = soroban_sdk::Vec::new(env); + for _ in 0..ic_count { + ic.push_back(g1.clone()); + } + + VerificationKey { + alpha: g1.clone(), + beta: g2.clone(), + gamma: g2.clone(), + delta: g2, + ic, + } + } + + fn make_proof(env: &Env) -> Groth16Proof { + let g1 = G1Point { + bytes: BytesN::from_array(env, &[0u8; 64]), + }; + let g2 = G2Point { + bytes: BytesN::from_array(env, &[0u8; 128]), + }; + + Groth16Proof { + a: g1.clone(), + b: g2, + c: g1, + } + } + + #[test] + fn test_apply_fog_of_war_transition_rejects_hidden_tile() { + let env = Env::default(); + let snapshot = FogOfWarSnapshot { + map_root: BytesN::from_array(&env, &[1u8; 32]), + explored_root: BytesN::from_array(&env, &[2u8; 32]), + origin_x: 0, + origin_y: 0, + visibility_radius: 2, + }; + let transition = FogOfWarTransition { + prior_explored_root: snapshot.explored_root.clone(), + next_explored_root: BytesN::from_array(&env, &[3u8; 32]), + tile_x: 3, + tile_y: 0, + }; + + let result = apply_fog_of_war_transition(&snapshot, &transition); + assert!(matches!(result, Err(ZKError::InvalidVisibility))); + } + + #[test] + fn test_fog_of_war_circuit_rejects_snapshot_above_max_radius() { + let env = Env::default(); + let circuit = FogOfWarCircuit::new(make_vk(&env, 9), 3); + let snapshot = FogOfWarSnapshot { + map_root: BytesN::from_array(&env, &[1u8; 32]), + explored_root: BytesN::from_array(&env, &[2u8; 32]), + origin_x: 0, + origin_y: 0, + visibility_radius: 4, + }; + let transition = FogOfWarTransition { + prior_explored_root: snapshot.explored_root.clone(), + next_explored_root: BytesN::from_array(&env, &[3u8; 32]), + tile_x: 1, + tile_y: 1, + }; + + let result = circuit.verify_exploration(&env, &make_proof(&env), &snapshot, &transition); + assert_eq!(result, Err(ZKError::InvalidVisibility)); + } + + #[test] + fn test_open_state_channel_requires_deadline() { + let env = Env::default(); + let result = open_state_channel( + BytesN::from_array(&env, &[1u8; 32]), + BytesN::from_array(&env, &[2u8; 32]), + BytesN::from_array(&env, &[3u8; 32]), + 0, + ); + + assert!(matches!(result, Err(ZKError::InvalidInput))); + } + + #[test] + fn test_apply_state_channel_transition_rejects_wrong_round() { + let env = Env::default(); + let channel = open_state_channel( + BytesN::from_array(&env, &[1u8; 32]), + BytesN::from_array(&env, &[2u8; 32]), + BytesN::from_array(&env, &[3u8; 32]), + 10, + ) + .unwrap(); + let transition = StateChannelTransition { + prior_state_root: channel.state_root.clone(), + next_state_root: BytesN::from_array(&env, &[4u8; 32]), + round: 2, + submitted_at: 5, + }; + + let result = apply_state_channel_transition(&channel, &transition); + assert!(matches!(result, Err(ZKError::InvalidStateTransition))); + } + + #[test] + fn test_state_channel_circuit_rejects_closed_channel() { + let env = Env::default(); + let channel = ZkStateChannel { + channel_id: BytesN::from_array(&env, &[1u8; 32]), + participants_root: BytesN::from_array(&env, &[2u8; 32]), + state_root: BytesN::from_array(&env, &[3u8; 32]), + round: 1, + dispute_deadline: 5, + closed: true, + }; + let transition = StateChannelTransition { + prior_state_root: channel.state_root.clone(), + next_state_root: BytesN::from_array(&env, &[4u8; 32]), + round: 2, + submitted_at: 5, + }; + let circuit = StateChannelCircuit::new(make_vk(&env, 7)); + + let result = circuit.verify_transition(&env, &make_proof(&env), &channel, &transition); + assert_eq!(result, Err(ZKError::ChannelClosed)); + } + + #[test] + fn test_compose_statement_roots_is_deterministic() { + let env = Env::default(); + let steps = [ + BytesN::from_array(&env, &[1u8; 32]), + BytesN::from_array(&env, &[2u8; 32]), + BytesN::from_array(&env, &[3u8; 32]), + ]; + + let root_a = compose_statement_roots(&env, &steps).unwrap(); + let root_b = compose_statement_roots(&env, &steps).unwrap(); + assert_eq!(root_a, root_b); + } + + #[test] + fn test_recursive_proof_layout_requires_non_empty_steps() { + let env = Env::default(); + let result = RecursiveProofLayout::from_step_roots( + &env, + BytesN::from_array(&env, &[1u8; 32]), + BytesN::from_array(&env, &[2u8; 32]), + &[], + ); + + assert!(matches!(result, Err(ZKError::InvalidProofComposition))); + } + + #[test] + fn test_recursive_proof_circuit_rejects_out_of_bounds_proof_count() { + let env = Env::default(); + let circuit = RecursiveProofCircuit::new(make_vk(&env, 5), 2); + let layout = RecursiveProofLayout { + initial_state_root: BytesN::from_array(&env, &[1u8; 32]), + final_state_root: BytesN::from_array(&env, &[2u8; 32]), + accumulator_root: BytesN::from_array(&env, &[3u8; 32]), + proof_count: 3, + }; + + let result = circuit.verify_composition(&env, &make_proof(&env), &layout); + assert_eq!(result, Err(ZKError::InvalidProofComposition)); + } +} diff --git a/src/zk/circuits.rs b/src/zk/circuits.rs index 3c312aa..9c1331e 100644 --- a/src/zk/circuits.rs +++ b/src/zk/circuits.rs @@ -11,9 +11,29 @@ use super::types::{Groth16Proof, Scalar, VerificationKey}; /// `[from_x, from_y, to_x, to_y, max_distance]`. /// /// # Example -/// ```ignore +/// ```no_run +/// use cougr_core::zk::{G1Point, G2Point}; +/// use cougr_core::zk::experimental::{Groth16Proof, MovementCircuit, VerificationKey}; +/// use soroban_sdk::{BytesN, Env, Vec}; +/// +/// let env = Env::default(); +/// let g1 = G1Point { bytes: BytesN::from_array(&env, &[0u8; 64]) }; +/// let g2 = G2Point { bytes: BytesN::from_array(&env, &[0u8; 128]) }; +/// let mut ic = Vec::new(&env); +/// for _ in 0..6 { +/// ic.push_back(g1.clone()); +/// } +/// let vk = VerificationKey { +/// alpha: g1.clone(), +/// beta: g2.clone(), +/// gamma: g2.clone(), +/// delta: g2, +/// ic, +/// }; +/// let proof = Groth16Proof { a: g1.clone(), b: vk.beta.clone(), c: g1 }; /// let circuit = MovementCircuit::new(vk, 10); -/// let valid = circuit.verify_move(&env, &proof, 0, 0, 3, 4)?; +/// let _valid = circuit.verify_move(&env, &proof, 0, 0, 3, 4)?; +/// # Ok::<(), cougr_core::zk::ZKError>(()) /// ``` pub struct MovementCircuit { pub vk: VerificationKey, @@ -180,10 +200,33 @@ impl TurnSequenceCircuit { /// Use this when you have a custom circuit not covered by the pre-built ones. /// /// # Example -/// ```ignore +/// ```no_run +/// use cougr_core::zk::{G1Point, G2Point}; +/// use cougr_core::zk::experimental::{ +/// bytes32_to_scalar, u32_to_scalar, CustomCircuit, GameCircuit, Groth16Proof, VerificationKey, +/// }; +/// use soroban_sdk::{BytesN, Env, Vec}; +/// +/// let env = Env::default(); +/// let g1 = G1Point { bytes: BytesN::from_array(&env, &[0u8; 64]) }; +/// let g2 = G2Point { bytes: BytesN::from_array(&env, &[0u8; 128]) }; +/// let mut ic = Vec::new(&env); +/// for _ in 0..3 { +/// ic.push_back(g1.clone()); +/// } +/// let vk = VerificationKey { +/// alpha: g1.clone(), +/// beta: g2.clone(), +/// gamma: g2.clone(), +/// delta: g2, +/// ic, +/// }; +/// let root = BytesN::from_array(&env, &[9u8; 32]); /// let inputs = vec![u32_to_scalar(&env, 42), bytes32_to_scalar(&root)]; /// let circuit = CustomCircuit::new(vk, inputs); -/// let valid = circuit.verify_with_inputs(&env, &proof, &circuit.public_inputs())?; +/// let proof = Groth16Proof { a: g1.clone(), b: circuit.verification_key().beta.clone(), c: g1 }; +/// let _valid = circuit.verify_with_inputs(&env, &proof, circuit.public_inputs())?; +/// # Ok::<(), cougr_core::zk::ZKError>(()) /// ``` pub struct CustomCircuit { vk: VerificationKey, diff --git a/src/zk/error.rs b/src/zk/error.rs index 08e8999..0973c38 100644 --- a/src/zk/error.rs +++ b/src/zk/error.rs @@ -33,4 +33,14 @@ pub enum ZKError { MaxDepthExceeded = 22, /// Leaf data is invalid or malformed. InvalidLeaf = 23, + /// A state transition violates the explicit phase-3 orchestration contract. + InvalidStateTransition = 24, + /// A transition arrived after its allowed dispute or reveal window. + DeadlineExpired = 25, + /// The requested operation cannot proceed because the channel is already closed. + ChannelClosed = 26, + /// A fog-of-war exploration target lies outside the allowed visibility window. + InvalidVisibility = 27, + /// A recursive composition descriptor is malformed or exceeds declared bounds. + InvalidProofComposition = 28, } diff --git a/src/zk/experimental.rs b/src/zk/experimental.rs new file mode 100644 index 0000000..2e79951 --- /dev/null +++ b/src/zk/experimental.rs @@ -0,0 +1,38 @@ +//! Experimental privacy and proof-verification surface for Cougr. +//! +//! These exports remain intentionally outside Cougr's stable privacy promise. + +pub use super::advanced::{ + apply_fog_of_war_transition, apply_state_channel_transition, close_state_channel, + compose_statement_roots, open_state_channel, FogOfWarCircuit, FogOfWarSnapshot, + FogOfWarTransition, RecursiveProofCircuit, RecursiveProofLayout, StateChannelCircuit, + StateChannelTransition, ZkStateChannel, +}; +pub use super::bls12_381::{ + bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g1_mul, bls12_381_pairing_check, +}; +pub use super::circuits::{ + CombatCircuit, CustomCircuit, CustomCircuitBuilder, InventoryCircuit, MovementCircuit, + TurnSequenceCircuit, +}; +pub use super::components::{ + ProofSubmission, VerifiedMarker, PROOF_SUBMISSION_TYPE, VERIFIED_MARKER_TYPE, +}; +#[cfg(feature = "hazmat-crypto")] +pub use super::crypto::{ + poseidon2_hash, poseidon2_hash_single, poseidon_permutation, Poseidon2Params, +}; +pub use super::groth16::{validate_groth16_contract, verify_groth16}; +pub use super::interfaces::Groth16ProofVerifier; +#[cfg(feature = "hazmat-crypto")] +pub use super::merkle::{ + verify_poseidon_proof, PoseidonMerkleProof, PoseidonMerkleTree, PoseidonSparseMerkleTree, +}; +pub use super::systems::{ + cleanup_verified_system, commit_reveal_deadline_system, decode_verified_at, + encode_commit_reveal, encode_verified_marker, verify_proofs_system, verify_proofs_with, +}; +pub use super::traits::{ + bytes32_to_scalar, i32_to_scalar, u32_to_scalar, u64_to_scalar, GameCircuit, +}; +pub use super::types::{Groth16Proof, VerificationKey}; diff --git a/src/zk/groth16.rs b/src/zk/groth16.rs index 6aca01b..3a663bf 100644 --- a/src/zk/groth16.rs +++ b/src/zk/groth16.rs @@ -21,16 +21,36 @@ use super::types::{Groth16Proof, Scalar, VerificationKey}; /// - `Ok(true)` if the proof is valid /// - `Ok(false)` if the pairing check fails /// - `Err(ZKError)` if inputs are malformed +/// +/// # Verification Contract +/// +/// This verifier treats the following as explicit contract guarantees: +/// - `vk.ic.len()` must equal `public_inputs.len() + 1` +/// - malformed verification-key shape returns `InvalidVerificationKey` +/// - mismatched pairing input lengths return `InvalidInput` +/// +/// This verifier does **not** currently promise stronger normalization or +/// subgroup-validation guarantees beyond what Soroban's BN254 host types +/// enforce when decoding points and scalars. For that reason the Groth16 +/// verification flow remains part of Cougr's experimental privacy surface. +pub fn validate_groth16_contract( + vk: &VerificationKey, + public_inputs: &[Scalar], +) -> Result<(), ZKError> { + if vk.ic.is_empty() || vk.ic.len() as usize != public_inputs.len() + 1 { + return Err(ZKError::InvalidVerificationKey); + } + + Ok(()) +} + pub fn verify_groth16( env: &Env, vk: &VerificationKey, proof: &Groth16Proof, public_inputs: &[Scalar], ) -> Result { - // IC must have exactly public_inputs.len() + 1 elements - if vk.ic.len() as usize != public_inputs.len() + 1 { - return Err(ZKError::InvalidVerificationKey); - } + validate_groth16_contract(vk, public_inputs)?; // Compute vk_x = IC[0] + sum(public_inputs[i] * IC[i+1]) let mut vk_x = vk.ic.get(0).ok_or(ZKError::InvalidVerificationKey)?; @@ -150,4 +170,27 @@ mod tests { let result = verify_groth16(&env, &vk, &proof, &[]); assert_eq!(result, Err(ZKError::InvalidVerificationKey)); } + + #[test] + fn test_validate_groth16_contract_accepts_matching_lengths() { + let env = Env::default(); + let g1 = make_g1(&env); + let g2 = make_g2(&env); + let mut ic = Vec::new(&env); + ic.push_back(g1.clone()); + ic.push_back(g1); + + let vk = VerificationKey { + alpha: make_g1(&env), + beta: g2.clone(), + gamma: g2.clone(), + delta: g2, + ic, + }; + + let scalar = Scalar { + bytes: BytesN::from_array(&env, &[0u8; 32]), + }; + assert_eq!(validate_groth16_contract(&vk, &[scalar]), Ok(())); + } } diff --git a/src/zk/interfaces.rs b/src/zk/interfaces.rs new file mode 100644 index 0000000..255fe03 --- /dev/null +++ b/src/zk/interfaces.rs @@ -0,0 +1,231 @@ +use soroban_sdk::{Bytes, BytesN, Env}; + +use super::commitment::{pedersen_commit, pedersen_verify, PedersenCommitment, PedersenParams}; +use super::error::ZKError; +use super::groth16::verify_groth16; +use super::merkle::proof::{verify_inclusion, OnChainMerkleProof}; +use super::types::{Groth16Proof, Scalar, VerificationKey}; + +/// Stable interface for commitment schemes used by privacy primitives. +/// +/// The trait contract is intentionally narrow: +/// - implementations must define exact parameter and opening semantics +/// - malformed inputs must return `Err(ZKError)` rather than silently succeeding +/// - `verify` must return `Ok(false)` only for a well-formed but invalid opening +pub trait CommitmentScheme { + type Parameters; + type Value; + type Opening; + type Commitment; + + fn commit( + &self, + env: &Env, + params: &Self::Parameters, + value: &Self::Value, + opening: &Self::Opening, + ) -> Result; + + fn verify( + &self, + env: &Env, + params: &Self::Parameters, + commitment: &Self::Commitment, + value: &Self::Value, + opening: &Self::Opening, + ) -> Result; +} + +/// Stable interface for Merkle inclusion proof verification. +/// +/// Implementations must reject malformed proofs with `Err(ZKError)` and +/// return `Ok(false)` only when the proof is well-formed but does not match +/// the expected root. +pub trait MerkleProofVerifier { + type Proof; + type Root; + + fn verify( + &self, + env: &Env, + proof: &Self::Proof, + expected_root: &Self::Root, + ) -> Result; +} + +/// Stable interface for hidden-state encoding. +/// +/// Hidden-state codecs define the exact byte-level representation used before +/// a caller commits to or stores private state metadata. +pub trait HiddenStateCodec { + type State; + + fn encode(&self, env: &Env, state: &Self::State) -> Result; + fn decode(&self, env: &Env, encoded: &Bytes) -> Result; +} + +/// Stable interface for proof verifiers. +/// +/// The interface itself is stable even when specific proof systems are not. +/// This allows callers to depend on a narrow verification contract while +/// choosing whether an implementation belongs to the stable or experimental +/// privacy surface. +pub trait ProofVerifier { + type VerificationKey; + type Proof; + type PublicInput; + + fn verify( + &self, + env: &Env, + verification_key: &Self::VerificationKey, + proof: &Self::Proof, + public_inputs: &[Self::PublicInput], + ) -> Result; +} + +/// Stable Pedersen commitment scheme adapter. +#[derive(Clone, Copy, Debug, Default)] +pub struct PedersenCommitmentScheme; + +impl CommitmentScheme for PedersenCommitmentScheme { + type Parameters = PedersenParams; + type Value = Scalar; + type Opening = Scalar; + type Commitment = PedersenCommitment; + + fn commit( + &self, + env: &Env, + params: &Self::Parameters, + value: &Self::Value, + opening: &Self::Opening, + ) -> Result { + pedersen_commit(env, params, value, opening) + } + + fn verify( + &self, + env: &Env, + params: &Self::Parameters, + commitment: &Self::Commitment, + value: &Self::Value, + opening: &Self::Opening, + ) -> Result { + pedersen_verify(env, params, commitment, value, opening) + } +} + +/// Stable SHA256 Merkle inclusion verifier adapter. +#[derive(Clone, Copy, Debug, Default)] +pub struct Sha256MerkleProofVerifier; + +impl MerkleProofVerifier for Sha256MerkleProofVerifier { + type Proof = OnChainMerkleProof; + type Root = BytesN<32>; + + fn verify( + &self, + env: &Env, + proof: &Self::Proof, + expected_root: &Self::Root, + ) -> Result { + verify_inclusion(env, proof, expected_root) + } +} + +/// Fixed-width hidden-state codec for 32-byte payloads. +/// +/// This is intentionally conservative: it only accepts a `BytesN<32>` state, +/// ensuring the encoded representation is deterministic and length-safe. +#[derive(Clone, Copy, Debug, Default)] +pub struct Bytes32HiddenStateCodec; + +impl HiddenStateCodec for Bytes32HiddenStateCodec { + type State = BytesN<32>; + + fn encode(&self, env: &Env, state: &Self::State) -> Result { + Ok(Bytes::from_slice(env, &state.to_array())) + } + + fn decode(&self, env: &Env, encoded: &Bytes) -> Result { + if encoded.len() != 32 { + return Err(ZKError::InvalidInput); + } + + let mut bytes = [0u8; 32]; + for i in 0..32u32 { + bytes[i as usize] = encoded.get(i).ok_or(ZKError::InvalidInput)?; + } + Ok(BytesN::from_array(env, &bytes)) + } +} + +/// Experimental Groth16 verifier adapter. +/// +/// The interface is explicit, but the underlying proof system remains +/// experimental until its assumptions and host-function behavior are hardened +/// further for a stable contract claim. +#[derive(Clone, Copy, Debug, Default)] +pub struct Groth16ProofVerifier; + +impl ProofVerifier for Groth16ProofVerifier { + type VerificationKey = VerificationKey; + type Proof = Groth16Proof; + type PublicInput = Scalar; + + fn verify( + &self, + env: &Env, + verification_key: &Self::VerificationKey, + proof: &Self::Proof, + public_inputs: &[Self::PublicInput], + ) -> Result { + verify_groth16(env, verification_key, proof, public_inputs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use soroban_sdk::{BytesN, Env, Vec}; + + #[test] + fn test_bytes32_hidden_state_codec_roundtrip() { + let env = Env::default(); + let codec = Bytes32HiddenStateCodec; + let state = BytesN::from_array(&env, &[0xAB; 32]); + + let encoded = codec.encode(&env, &state).unwrap(); + let decoded = codec.decode(&env, &encoded).unwrap(); + + assert_eq!(decoded, state); + } + + #[test] + fn test_bytes32_hidden_state_codec_rejects_wrong_length() { + let env = Env::default(); + let codec = Bytes32HiddenStateCodec; + let encoded = Bytes::from_slice(&env, &[1, 2, 3]); + + let result = codec.decode(&env, &encoded); + assert_eq!(result, Err(ZKError::InvalidInput)); + } + + #[test] + fn test_sha256_merkle_verifier_rejects_malformed_proof() { + let env = Env::default(); + let verifier = Sha256MerkleProofVerifier; + let proof = OnChainMerkleProof { + siblings: Vec::new(&env), + path_bits: 0, + leaf: BytesN::from_array(&env, &[1u8; 32]), + leaf_index: 0, + depth: 1, + }; + let root = BytesN::from_array(&env, &[0u8; 32]); + + let result = verifier.verify(&env, &proof, &root); + assert_eq!(result, Err(ZKError::InvalidProofLength)); + } +} diff --git a/src/zk/merkle/tree.rs b/src/zk/merkle/tree.rs index 40b762e..3656b9f 100644 --- a/src/zk/merkle/tree.rs +++ b/src/zk/merkle/tree.rs @@ -12,10 +12,16 @@ pub const MAX_DEPTH: u32 = 20; /// Constructed from a list of leaf hashes. Supports inclusion proof generation. /// /// # Example -/// ```ignore -/// let leaves = vec![[1u8; 32], [2u8; 32], [3u8; 32], [4u8; 32]]; +/// ``` +/// use cougr_core::zk::stable::MerkleTree; +/// use soroban_sdk::Env; +/// +/// let env = Env::default(); +/// let leaves = [[1u8; 32], [2u8; 32], [3u8; 32], [4u8; 32]]; /// let tree = MerkleTree::from_leaves(&env, &leaves)?; /// let proof = tree.proof(2)?; +/// assert_eq!(proof.leaf_index, 2); +/// # Ok::<(), cougr_core::zk::ZKError>(()) /// ``` pub struct MerkleTree { depth: u32, diff --git a/src/zk/mod.rs b/src/zk/mod.rs index d375e66..0ae1765 100644 --- a/src/zk/mod.rs +++ b/src/zk/mod.rs @@ -5,58 +5,80 @@ //! //! ## Architecture //! -//! - **`types`**: Core ZK types (`G1Point`, `G2Point`, `Scalar`, `Groth16Proof`, `VerificationKey`) -//! - **`crypto`**: Low-level BN254 and Poseidon wrappers -//! - **`groth16`**: Groth16 proof verification +//! - **`stable`**: stable privacy primitives and interfaces +//! - **`experimental`**: advanced proof-verification flows and automation +//! - **`types`**: core ZK types (`G1Point`, `G2Point`, `Scalar`, `Groth16Proof`, `VerificationKey`) +//! - **`crypto`**: low-level BN254 and Poseidon wrappers +//! - **`groth16`**: Groth16 proof verification contract (experimental implementation) //! - **`error`**: ZK-specific error types //! - **`testing`**: Mock types for unit testing without real proofs //! +//! ## Maturity Split +//! +//! Stable privacy surface: +//! +//! - commitments +//! - commit-reveal +//! - hidden-state encoding +//! - Merkle inclusion and sparse Merkle utilities +//! - privacy interfaces such as `CommitmentScheme`, `MerkleProofVerifier`, and `HiddenStateCodec` +//! +//! Experimental privacy surface: +//! +//! - Groth16 verification flows +//! - proof-submission execution helpers +//! - prebuilt verification circuits +//! - hazmat Poseidon-based privacy tooling +//! //! ## Usage //! -//! ```ignore -//! use cougr_core::zk::{crypto, groth16, types::*}; +//! ```no_run +//! use cougr_core::zk::experimental::{verify_groth16, Groth16Proof, VerificationKey}; +//! use cougr_core::zk::{G1Point, G2Point, Scalar}; +//! use soroban_sdk::{BytesN, Env, Vec}; //! -//! // Verify a Groth16 proof on-chain -//! let result = groth16::verify_groth16(&env, &vk, &proof, &public_inputs); +//! let env = Env::default(); +//! let g1 = G1Point { bytes: BytesN::from_array(&env, &[0u8; 64]) }; +//! let g2 = G2Point { bytes: BytesN::from_array(&env, &[0u8; 128]) }; +//! let vk = VerificationKey { +//! alpha: g1.clone(), +//! beta: g2.clone(), +//! gamma: g2.clone(), +//! delta: g2, +//! ic: Vec::from_array(&env, [g1.clone()]), +//! }; +//! let proof = Groth16Proof { a: g1.clone(), b: vk.beta.clone(), c: g1 }; +//! let public_inputs: [Scalar; 0] = []; +//! let _result = verify_groth16(&env, &vk, &proof, &public_inputs); //! ``` -pub mod bls12_381; -pub mod circuits; -pub mod commitment; -pub mod components; -pub mod crypto; -pub mod error; -pub mod groth16; -pub mod merkle; -pub mod systems; -pub mod testing; -pub mod traits; -pub mod types; +pub(crate) mod advanced; +pub(crate) mod bls12_381; +pub(crate) mod circuits; +pub(crate) mod commitment; +pub(crate) mod components; +pub(crate) mod crypto; +pub(crate) mod error; +pub mod experimental; +pub(crate) mod groth16; +pub(crate) mod interfaces; +pub(crate) mod merkle; +pub mod stable; +pub(crate) mod systems; +#[cfg(any(test, feature = "testutils"))] +pub(crate) mod testing; +pub(crate) mod traits; +pub(crate) mod types; -// Re-export commonly used items -pub use bls12_381::{ - bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g1_mul, bls12_381_pairing_check, -}; -pub use circuits::{ - CombatCircuit, CustomCircuit, CustomCircuitBuilder, InventoryCircuit, MovementCircuit, - TurnSequenceCircuit, -}; -pub use commitment::{pedersen_commit, pedersen_verify, PedersenCommitment, PedersenParams}; -pub use components::{CommitReveal, HiddenState, ProofSubmission, VerifiedMarker}; -#[cfg(feature = "hazmat-crypto")] -pub use crypto::{poseidon2_hash, poseidon2_hash_single, Poseidon2Params}; +// Stable-by-default root exports. pub use error::ZKError; -pub use groth16::verify_groth16; -pub use merkle::{verify_inclusion, MerkleProof, MerkleTree, OnChainMerkleProof, SparseMerkleTree}; -#[cfg(feature = "hazmat-crypto")] -pub use merkle::{ - verify_poseidon_proof, PoseidonMerkleProof, PoseidonMerkleTree, PoseidonSparseMerkleTree, -}; -pub use systems::{ - cleanup_verified_system, commit_reveal_deadline_system, encode_commit_reveal, - encode_verified_marker, verify_proofs_system, +pub use stable::{ + commit_reveal_deadline_system, encode_commit_reveal, pedersen_commit, pedersen_verify, + verify_inclusion, Bytes32HiddenStateCodec, CommitReveal, CommitmentScheme, HiddenState, + HiddenStateCodec, MerkleProof, MerkleProofVerifier, MerkleTree, OnChainMerkleProof, + PedersenCommitment, PedersenCommitmentScheme, PedersenParams, ProofVerifier, + Sha256MerkleProofVerifier, SparseMerkleTree, COMMIT_REVEAL_TYPE, HIDDEN_STATE_TYPE, }; -pub use traits::{bytes32_to_scalar, i32_to_scalar, u32_to_scalar, GameCircuit}; pub use types::{ Bls12381G1Point, Bls12381G2Point, Bls12381Scalar, G1Point, G2Point, Groth16Proof, Scalar, VerificationKey, diff --git a/src/zk/stable.rs b/src/zk/stable.rs new file mode 100644 index 0000000..ec0b200 --- /dev/null +++ b/src/zk/stable.rs @@ -0,0 +1,17 @@ +//! Stable privacy surface for Cougr. +//! +//! This module groups the privacy primitives whose contracts are intentionally +//! documented and treated as the stable privacy subset during phase 2. + +pub use super::commitment::{pedersen_commit, pedersen_verify, PedersenCommitment, PedersenParams}; +pub use super::components::{CommitReveal, HiddenState, COMMIT_REVEAL_TYPE, HIDDEN_STATE_TYPE}; +pub use super::interfaces::{ + Bytes32HiddenStateCodec, CommitmentScheme, HiddenStateCodec, MerkleProofVerifier, + PedersenCommitmentScheme, ProofVerifier, Sha256MerkleProofVerifier, +}; +pub use super::merkle::proof::to_on_chain_proof; +pub use super::merkle::tree::verify_proof; +pub use super::merkle::{ + verify_inclusion, MerkleProof, MerkleTree, OnChainMerkleProof, SparseMerkleTree, +}; +pub use super::systems::{commit_reveal_deadline_system, encode_commit_reveal}; diff --git a/src/zk/systems.rs b/src/zk/systems.rs index 45734f5..71ab0c9 100644 --- a/src/zk/systems.rs +++ b/src/zk/systems.rs @@ -2,7 +2,7 @@ use crate::simple_world::SimpleWorld; use soroban_sdk::{Bytes, BytesN, Env, Symbol}; use super::components::{COMMIT_REVEAL_TYPE, VERIFIED_MARKER_TYPE}; -use super::groth16::verify_groth16; +use super::interfaces::{Groth16ProofVerifier, ProofVerifier}; use super::types::{Groth16Proof, Scalar, VerificationKey}; /// Read a big-endian `u64` from `data` at byte offset `offset`. @@ -54,25 +54,47 @@ pub fn encode_commit_reveal( /// verification, a `VerifiedMarker` component is added to the entity. /// /// Returns `true` if the proof was valid. -pub fn verify_proofs_system( +pub fn verify_proofs_with< + V: ProofVerifier, +>( world: &mut SimpleWorld, env: &Env, entity_id: u32, + verifier: &V, vk: &VerificationKey, proof: &Groth16Proof, public_inputs: &[Scalar], -) -> bool { +) -> Result { let verified_sym = Symbol::new(env, VERIFIED_MARKER_TYPE); + let is_valid = verifier.verify(env, vk, proof, public_inputs)?; - match verify_groth16(env, vk, proof, public_inputs) { - Ok(true) => { - let now = env.ledger().timestamp(); - let marker_data = encode_verified_marker(env, now); - world.add_component(entity_id, verified_sym, marker_data); - true - } - _ => false, + if is_valid { + let now = env.ledger().timestamp(); + let marker_data = encode_verified_marker(env, now); + world.add_component(entity_id, verified_sym, marker_data); } + + Ok(is_valid) +} + +pub fn verify_proofs_system( + world: &mut SimpleWorld, + env: &Env, + entity_id: u32, + vk: &VerificationKey, + proof: &Groth16Proof, + public_inputs: &[Scalar], +) -> bool { + verify_proofs_with( + world, + env, + entity_id, + &Groth16ProofVerifier, + vk, + proof, + public_inputs, + ) + .unwrap_or(false) } /// Check for expired commit-reveal deadlines. @@ -122,8 +144,27 @@ pub fn cleanup_verified_system(world: &mut SimpleWorld, env: &Env, max_age: u64) #[cfg(test)] mod tests { use super::*; + use crate::zk::error::ZKError; use soroban_sdk::Env; + struct RejectingVerifier; + + impl ProofVerifier for RejectingVerifier { + type VerificationKey = VerificationKey; + type Proof = Groth16Proof; + type PublicInput = Scalar; + + fn verify( + &self, + _env: &Env, + _verification_key: &Self::VerificationKey, + _proof: &Self::Proof, + _public_inputs: &[Self::PublicInput], + ) -> Result { + Ok(false) + } + } + #[test] fn test_commit_reveal_deadline_keeps_non_expired() { let env = Env::default(); @@ -178,4 +219,43 @@ mod tests { cleanup_verified_system(&mut world, &env, 1000); assert!(world.has_component(e1, &verified_sym)); } + + #[test] + fn test_verify_proofs_with_invalid_result_does_not_mark_entity() { + let env = Env::default(); + let mut world = SimpleWorld::new(&env); + let entity_id = world.spawn_entity(); + let verifier = RejectingVerifier; + let vk = VerificationKey { + alpha: super::super::types::G1Point { + bytes: BytesN::from_array(&env, &[0u8; 64]), + }, + beta: super::super::types::G2Point { + bytes: BytesN::from_array(&env, &[0u8; 128]), + }, + gamma: super::super::types::G2Point { + bytes: BytesN::from_array(&env, &[0u8; 128]), + }, + delta: super::super::types::G2Point { + bytes: BytesN::from_array(&env, &[0u8; 128]), + }, + ic: soroban_sdk::Vec::new(&env), + }; + let proof = Groth16Proof { + a: super::super::types::G1Point { + bytes: BytesN::from_array(&env, &[0u8; 64]), + }, + b: super::super::types::G2Point { + bytes: BytesN::from_array(&env, &[0u8; 128]), + }, + c: super::super::types::G1Point { + bytes: BytesN::from_array(&env, &[0u8; 64]), + }, + }; + + let result = + verify_proofs_with(&mut world, &env, entity_id, &verifier, &vk, &proof, &[]).unwrap(); + assert!(!result); + assert!(!world.has_component(entity_id, &Symbol::new(&env, VERIFIED_MARKER_TYPE))); + } } diff --git a/src/zk/testing.rs b/src/zk/testing.rs index 53957bc..fd4e54c 100644 --- a/src/zk/testing.rs +++ b/src/zk/testing.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use soroban_sdk::{BytesN, Env, Vec}; use super::types::{G1Point, G2Point, Groth16Proof, Scalar, VerificationKey}; diff --git a/src/zk/traits.rs b/src/zk/traits.rs index a62a819..6e619b0 100644 --- a/src/zk/traits.rs +++ b/src/zk/traits.rs @@ -12,11 +12,26 @@ use super::types::{Groth16Proof, Scalar, VerificationKey}; /// method handles the common Groth16 verification flow. /// /// # Example -/// ```ignore -/// use cougr_core::zk::traits::GameCircuit; +/// ```no_run +/// use cougr_core::zk::{G1Point, G2Point, Scalar}; +/// use cougr_core::zk::experimental::{GameCircuit, Groth16Proof, MovementCircuit, VerificationKey}; +/// use soroban_sdk::{BytesN, Env, Vec}; /// +/// let env = Env::default(); +/// let g1 = G1Point { bytes: BytesN::from_array(&env, &[0u8; 64]) }; +/// let g2 = G2Point { bytes: BytesN::from_array(&env, &[0u8; 128]) }; +/// let vk = VerificationKey { +/// alpha: g1.clone(), +/// beta: g2.clone(), +/// gamma: g2.clone(), +/// delta: g2, +/// ic: Vec::from_array(&env, [g1.clone()]), +/// }; +/// let proof = Groth16Proof { a: g1.clone(), b: vk.beta.clone(), c: g1 }; +/// let public_inputs: [Scalar; 0] = []; /// let circuit = MovementCircuit::new(vk, 10); -/// let result = circuit.verify_with_inputs(&env, &proof, &public_inputs)?; +/// let _result = circuit.verify_with_inputs(&env, &proof, &public_inputs)?; +/// # Ok::<(), cougr_core::zk::ZKError>(()) /// ``` pub trait GameCircuit { /// Get the verification key for this circuit. @@ -53,6 +68,15 @@ pub fn i32_to_scalar(env: &Env, val: i32) -> Scalar { } } +/// Convert a `u64` value to a BN254 scalar (little-endian, zero-padded to 32 bytes). +pub fn u64_to_scalar(env: &Env, val: u64) -> Scalar { + let mut bytes = [0u8; 32]; + bytes[..8].copy_from_slice(&val.to_le_bytes()); + Scalar { + bytes: BytesN::from_array(env, &bytes), + } +} + /// Convert a `BytesN<32>` directly to a `Scalar` (identity mapping). pub fn bytes32_to_scalar(val: &BytesN<32>) -> Scalar { Scalar { bytes: val.clone() } @@ -101,4 +125,12 @@ mod tests { let s = bytes32_to_scalar(&b); assert_eq!(s.bytes, b); } + + #[test] + fn test_u64_to_scalar() { + let env = Env::default(); + let s = u64_to_scalar(&env, 0x0102_0304_0506_0708); + let arr = s.bytes.to_array(); + assert_eq!(arr[..8], [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]); + } } diff --git a/tests/edge_cases.rs b/tests/edge_cases.rs index 750fced..028b9cf 100644 --- a/tests/edge_cases.rs +++ b/tests/edge_cases.rs @@ -3,12 +3,10 @@ //! Tests empty world operations, non-existent entities, double operations, //! component data boundaries, and other corner cases. -use cougr_core::commands::CommandQueue; -use cougr_core::component::ComponentStorage; -use cougr_core::observers::{ComponentEvent, ObservedWorld}; -use cougr_core::plugin::{Plugin, PluginApp}; -use cougr_core::query::SimpleQueryCache; -use cougr_core::simple_world::SimpleWorld; +use cougr_core::observers::ComponentEvent; +use cougr_core::{ + CommandQueue, ComponentStorage, ObservedWorld, Plugin, PluginApp, SimpleQueryCache, SimpleWorld, +}; use soroban_sdk::{symbol_short, Bytes, Env}; // --------------------------------------------------------------------------- diff --git a/tests/integration_accounts.rs b/tests/integration_accounts.rs index a00fd78..0414062 100644 --- a/tests/integration_accounts.rs +++ b/tests/integration_accounts.rs @@ -3,11 +3,10 @@ //! Tests session key lifecycle, BatchBuilder with MockAccount, //! SessionStorage persistence, recovery flow, and DeviceManager. -use cougr_core::accounts::error::AccountError; -use cougr_core::accounts::multi_device::{DeviceManager, DevicePolicy, MultiDeviceProvider}; -use cougr_core::accounts::recovery::{RecoverableAccount, RecoveryConfig, RecoveryProvider}; -use cougr_core::accounts::storage::SessionStorage; -use cougr_core::accounts::types::{SessionKey, SessionScope}; +use cougr_core::accounts::{ + AccountError, DeviceManager, DevicePolicy, MultiDeviceProvider, RecoverableAccount, + RecoveryConfig, RecoveryProvider, SessionKey, SessionScope, SessionStorage, +}; use soroban_sdk::{ contract, contractimpl, symbol_short, testutils::Address as _, vec, Address, BytesN, Env, }; @@ -40,6 +39,7 @@ fn test_session_key_lifecycle() { }, created_at: 0, operations_used: 0, + next_nonce: 0, }; // 2. Store @@ -64,6 +64,7 @@ fn test_session_key_lifecycle() { }, created_at: 0, operations_used: 0, + next_nonce: 0, }; SessionStorage::store(&env, &addr, &key2); @@ -102,6 +103,7 @@ fn test_session_storage_multiple_accounts() { }, created_at: 0, operations_used: 0, + next_nonce: 0, }; let key2 = SessionKey { key_id: BytesN::from_array(&env, &[2u8; 32]), @@ -112,6 +114,7 @@ fn test_session_storage_multiple_accounts() { }, created_at: 0, operations_used: 0, + next_nonce: 0, }; // Store keys for different accounts diff --git a/tests/integration_cross_module.rs b/tests/integration_cross_module.rs index fb3f787..6188d40 100644 --- a/tests/integration_cross_module.rs +++ b/tests/integration_cross_module.rs @@ -5,13 +5,11 @@ //! SimpleQueryCache + TrackedWorld, etc. use core::sync::atomic::{AtomicU32, Ordering}; -use cougr_core::change_tracker::TrackedWorld; -use cougr_core::commands::CommandQueue; -use cougr_core::observers::{ComponentEvent, ObservedWorld}; -use cougr_core::plugin::{Plugin, PluginApp}; -use cougr_core::query::SimpleQueryCache; +use cougr_core::observers::ComponentEvent; use cougr_core::scheduler::SimpleScheduler; -use cougr_core::simple_world::SimpleWorld; +use cougr_core::{ + CommandQueue, ObservedWorld, Plugin, PluginApp, SimpleQueryCache, SimpleWorld, TrackedWorld, +}; use soroban_sdk::{symbol_short, Bytes, Env}; // --------------------------------------------------------------------------- diff --git a/tests/integration_ecs.rs b/tests/integration_ecs.rs index 23c2102..aab98ca 100644 --- a/tests/integration_ecs.rs +++ b/tests/integration_ecs.rs @@ -2,12 +2,10 @@ //! //! Tests full game-loop patterns: spawn -> add components -> run systems -> query -> verify. -use cougr_core::commands::CommandQueue; -use cougr_core::component::ComponentStorage; -use cougr_core::plugin::{Plugin, PluginApp}; -use cougr_core::query::SimpleQueryCache; use cougr_core::scheduler::SimpleScheduler; -use cougr_core::simple_world::SimpleWorld; +use cougr_core::{ + CommandQueue, ComponentStorage, Plugin, PluginApp, SimpleQueryCache, SimpleWorld, +}; use soroban_sdk::{symbol_short, Bytes, Env}; // --------------------------------------------------------------------------- diff --git a/tests/integration_standards.rs b/tests/integration_standards.rs new file mode 100644 index 0000000..abbc4dc --- /dev/null +++ b/tests/integration_standards.rs @@ -0,0 +1,232 @@ +//! Integration tests for the reusable standards layer. + +use cougr_core::standards::{ + AccessControl, BatchExecutor, DelayedExecutionPolicy, ExecutionGuard, Ownable, Ownable2Step, + Pausable, RecoveryGuard, StandardsError, DEFAULT_ADMIN_ROLE_NAME, +}; +use soroban_sdk::{ + contract, contractimpl, symbol_short, testutils::Address as _, testutils::Ledger as _, Address, + Bytes, Env, Symbol, +}; + +#[contract] +pub struct TestContract; + +#[contractimpl] +impl TestContract {} + +#[test] +fn ownable_supports_transfer_and_renounce() { + let env = Env::default(); + let contract_id = env.register(TestContract, ()); + let owner = Address::generate(&env); + let next_owner = Address::generate(&env); + let module = Ownable::new(symbol_short!("own")); + + env.as_contract(&contract_id, || { + module.initialize(&env, &owner).unwrap(); + assert_eq!(module.owner(&env), Some(owner.clone())); + + module + .transfer_ownership(&env, &owner, &next_owner) + .unwrap(); + assert_eq!(module.owner(&env), Some(next_owner.clone())); + + module.renounce_ownership(&env, &next_owner).unwrap(); + assert_eq!(module.owner(&env), None); + }); +} + +#[test] +fn ownable_2step_requires_pending_owner_acceptance() { + let env = Env::default(); + let contract_id = env.register(TestContract, ()); + let owner = Address::generate(&env); + let pending_owner = Address::generate(&env); + let outsider = Address::generate(&env); + let module = Ownable2Step::new(symbol_short!("own2")); + + env.as_contract(&contract_id, || { + module.initialize(&env, &owner).unwrap(); + module.begin_transfer(&env, &owner, &pending_owner).unwrap(); + + let unauthorized = module.accept_transfer(&env, &outsider); + assert_eq!(unauthorized, Err(StandardsError::PendingOwnerMismatch)); + + module.accept_transfer(&env, &pending_owner).unwrap(); + assert_eq!(module.owner(&env), Some(pending_owner)); + assert_eq!(module.pending_owner(&env), None); + }); +} + +#[test] +fn access_control_enforces_admin_hierarchy() { + let env = Env::default(); + let contract_id = env.register(TestContract, ()); + let admin = Address::generate(&env); + let operator = Address::generate(&env); + let grantee = Address::generate(&env); + let module = AccessControl::new(symbol_short!("acl")); + let ops_role = symbol_short!("OPS"); + let ops_admin_role = symbol_short!("OPADM"); + + env.as_contract(&contract_id, || { + module.initialize(&env, &admin).unwrap(); + module + .grant_role(&env, &admin, &ops_role, &operator) + .unwrap(); + assert!(module.has_role(&env, &ops_role, &operator)); + + module + .set_role_admin(&env, &admin, &ops_role, &ops_admin_role) + .unwrap(); + module + .grant_role(&env, &admin, &ops_admin_role, &operator) + .unwrap(); + module + .grant_role(&env, &operator, &ops_role, &grantee) + .unwrap(); + assert!(module.has_role(&env, &ops_role, &grantee)); + + let default_admin_role = Symbol::new(&env, DEFAULT_ADMIN_ROLE_NAME); + module + .revoke_role(&env, &admin, &default_admin_role, &admin) + .unwrap(); + assert!(!module.has_role(&env, &default_admin_role, &admin)); + }); +} + +#[test] +fn pausable_blocks_execution_until_unpaused() { + let env = Env::default(); + let contract_id = env.register(TestContract, ()); + let caller = Address::generate(&env); + let module = Pausable::new(symbol_short!("pause")); + + env.as_contract(&contract_id, || { + assert!(!module.is_paused(&env)); + module.pause(&env, &caller).unwrap(); + assert_eq!(module.require_not_paused(&env), Err(StandardsError::Paused)); + module.unpause(&env, &caller).unwrap(); + assert!(module.require_not_paused(&env).is_ok()); + }); +} + +#[test] +fn execution_guard_prevents_reentrancy_like_nesting() { + let env = Env::default(); + let contract_id = env.register(TestContract, ()); + let module = ExecutionGuard::new(symbol_short!("exec")); + + env.as_contract(&contract_id, || { + module.enter(&env).unwrap(); + assert_eq!(module.enter(&env), Err(StandardsError::ExecutionLocked)); + module.exit(&env).unwrap(); + + let value = module.execute(&env, || 42u32).unwrap(); + assert_eq!(value, 42); + assert!(!module.is_locked(&env)); + }); +} + +#[test] +fn recovery_guard_blocks_sensitive_paths_while_active() { + let env = Env::default(); + let contract_id = env.register(TestContract, ()); + let caller = Address::generate(&env); + let module = RecoveryGuard::new(symbol_short!("recov")); + + env.as_contract(&contract_id, || { + module.activate(&env, &caller).unwrap(); + assert_eq!( + module.require_inactive(&env), + Err(StandardsError::RecoveryActive) + ); + module.clear(&env, &caller).unwrap(); + assert!(module.require_inactive(&env).is_ok()); + }); +} + +#[test] +fn batch_executor_enforces_limits_and_executes_items() { + let batch = BatchExecutor::new(3); + let items = [1u32, 2, 3]; + + let results = batch.execute(&items, |item| Ok(item * 2)).unwrap(); + assert_eq!(results, vec![2, 4, 6]); + + assert_eq!( + batch.execute::(&[], |_| Ok(0)), + Err(StandardsError::BatchEmpty) + ); + assert_eq!( + batch.execute(&[1u32, 2, 3, 4], |item| Ok(*item)), + Err(StandardsError::BatchTooLarge) + ); +} + +#[test] +fn delayed_execution_enforces_readiness_and_expiry() { + let env = Env::default(); + let contract_id = env.register(TestContract, ()); + let module = DelayedExecutionPolicy::new(symbol_short!("delay")); + + env.as_contract(&contract_id, || { + env.ledger().with_mut(|li| { + li.timestamp = 100; + }); + + let scheduled = module + .schedule( + &env, + symbol_short!("UPGD"), + Bytes::from_array(&env, &[1, 2, 3]), + 10, + 20, + ) + .unwrap(); + assert_eq!(scheduled.operation_id, 1); + assert_eq!(module.pending_operations(&env).len(), 1); + + let too_early = module.execute_ready(&env, scheduled.operation_id); + assert_eq!(too_early, Err(StandardsError::OperationNotReady)); + + env.ledger().with_mut(|li| { + li.timestamp = 111; + }); + module.execute_ready(&env, scheduled.operation_id).unwrap(); + assert_eq!(module.pending_operations(&env).len(), 0); + + let second = module + .schedule( + &env, + symbol_short!("CANC"), + Bytes::from_array(&env, &[9]), + 5, + 5, + ) + .unwrap(); + module.cancel(&env, second.operation_id).unwrap(); + assert_eq!( + module.execute_ready(&env, second.operation_id), + Err(StandardsError::OperationNotFound) + ); + + let third = module + .schedule( + &env, + symbol_short!("EXPR"), + Bytes::from_array(&env, &[8]), + 1, + 1, + ) + .unwrap(); + env.ledger().with_mut(|li| { + li.timestamp = 114; + }); + assert_eq!( + module.execute_ready(&env, third.operation_id), + Err(StandardsError::OperationExpired) + ); + }); +} diff --git a/tests/integration_zk.rs b/tests/integration_zk.rs index 4a116bf..32a767f 100644 --- a/tests/integration_zk.rs +++ b/tests/integration_zk.rs @@ -3,12 +3,13 @@ //! Tests ZK components lifecycle, commit-reveal flow, cleanup systems, //! and byte encoding round-trips. -use cougr_core::simple_world::SimpleWorld; -use cougr_core::zk::components::{COMMIT_REVEAL_TYPE, HIDDEN_STATE_TYPE, VERIFIED_MARKER_TYPE}; -use cougr_core::zk::systems::{ - cleanup_verified_system, commit_reveal_deadline_system, encode_commit_reveal, - encode_verified_marker, +use cougr_core::zk::experimental::{ + cleanup_verified_system, encode_verified_marker, VERIFIED_MARKER_TYPE, }; +use cougr_core::zk::stable::{ + commit_reveal_deadline_system, encode_commit_reveal, COMMIT_REVEAL_TYPE, HIDDEN_STATE_TYPE, +}; +use cougr_core::SimpleWorld; use soroban_sdk::{symbol_short, Bytes, BytesN, Env, Symbol}; // --------------------------------------------------------------------------- diff --git a/tests/integration_zk_phase3.rs b/tests/integration_zk_phase3.rs new file mode 100644 index 0000000..88319c1 --- /dev/null +++ b/tests/integration_zk_phase3.rs @@ -0,0 +1,101 @@ +//! Phase-3 ZK integration tests. +//! +//! These cover orchestration contracts for the advanced experimental surface: +//! fog-of-war transitions, multiplayer state channels, and recursive layouts. + +use cougr_core::zk::experimental::{ + apply_fog_of_war_transition, apply_state_channel_transition, close_state_channel, + compose_statement_roots, open_state_channel, FogOfWarSnapshot, FogOfWarTransition, + RecursiveProofLayout, StateChannelTransition, +}; +use cougr_core::zk::ZKError; +use soroban_sdk::{BytesN, Env}; + +#[test] +fn fog_of_war_transition_updates_explored_root() { + let env = Env::default(); + let snapshot = FogOfWarSnapshot { + map_root: BytesN::from_array(&env, &[1u8; 32]), + explored_root: BytesN::from_array(&env, &[2u8; 32]), + origin_x: 5, + origin_y: 5, + visibility_radius: 2, + }; + let transition = FogOfWarTransition { + prior_explored_root: snapshot.explored_root.clone(), + next_explored_root: BytesN::from_array(&env, &[3u8; 32]), + tile_x: 6, + tile_y: 6, + }; + + let updated = apply_fog_of_war_transition(&snapshot, &transition).unwrap(); + assert_eq!(updated.explored_root, transition.next_explored_root); +} + +#[test] +fn state_channel_transition_and_close_flow() { + let env = Env::default(); + let channel = open_state_channel( + BytesN::from_array(&env, &[1u8; 32]), + BytesN::from_array(&env, &[2u8; 32]), + BytesN::from_array(&env, &[3u8; 32]), + 100, + ) + .unwrap(); + let transition = StateChannelTransition { + prior_state_root: channel.state_root.clone(), + next_state_root: BytesN::from_array(&env, &[4u8; 32]), + round: 1, + submitted_at: 42, + }; + + let updated = apply_state_channel_transition(&channel, &transition).unwrap(); + assert_eq!(updated.state_root, transition.next_state_root); + assert_eq!(updated.round, 1); + + let closed = close_state_channel(&updated, &updated.state_root, updated.round, 50).unwrap(); + assert!(closed.closed); + assert_eq!(closed.dispute_deadline, 50); +} + +#[test] +fn state_channel_transition_rejects_late_submission() { + let env = Env::default(); + let channel = open_state_channel( + BytesN::from_array(&env, &[1u8; 32]), + BytesN::from_array(&env, &[2u8; 32]), + BytesN::from_array(&env, &[3u8; 32]), + 10, + ) + .unwrap(); + let transition = StateChannelTransition { + prior_state_root: channel.state_root.clone(), + next_state_root: BytesN::from_array(&env, &[4u8; 32]), + round: 1, + submitted_at: 11, + }; + + let result = apply_state_channel_transition(&channel, &transition); + assert!(matches!(result, Err(ZKError::DeadlineExpired))); +} + +#[test] +fn recursive_layout_matches_composed_statement_root() { + let env = Env::default(); + let step_roots = [ + BytesN::from_array(&env, &[7u8; 32]), + BytesN::from_array(&env, &[8u8; 32]), + ]; + + let accumulator = compose_statement_roots(&env, &step_roots).unwrap(); + let layout = RecursiveProofLayout::from_step_roots( + &env, + BytesN::from_array(&env, &[1u8; 32]), + BytesN::from_array(&env, &[2u8; 32]), + &step_roots, + ) + .unwrap(); + + assert_eq!(layout.accumulator_root, accumulator); + assert_eq!(layout.proof_count, 2); +} diff --git a/tests/no_deprecated_attrs.rs b/tests/no_deprecated_attrs.rs new file mode 100644 index 0000000..21f60fb --- /dev/null +++ b/tests/no_deprecated_attrs.rs @@ -0,0 +1,48 @@ +use std::fs; +use std::path::{Path, PathBuf}; + +fn collect_rs_files(root: &Path, out: &mut Vec) { + if !root.exists() { + return; + } + + for entry in fs::read_dir(root).expect("read_dir failed") { + let entry = entry.expect("dir entry failed"); + let path = entry.path(); + if path.is_dir() { + collect_rs_files(&path, out); + } else if path.extension().and_then(|ext| ext.to_str()) == Some("rs") { + out.push(path); + } + } +} + +#[test] +fn source_tree_has_no_deprecated_attributes() { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let src_dir = manifest_dir.join("src"); + let mut files = Vec::new(); + collect_rs_files(&src_dir, &mut files); + + let offenders: Vec = files + .into_iter() + .filter_map(|path| { + let contents = fs::read_to_string(&path).expect("read_to_string failed"); + if contents.contains("#[deprecated") { + Some( + path.strip_prefix(&manifest_dir) + .unwrap_or(&path) + .display() + .to_string(), + ) + } else { + None + } + }) + .collect(); + + assert!( + offenders.is_empty(), + "unexpected deprecated attributes in source tree: {offenders:?}" + ); +} diff --git a/tests/public_api_surface.rs b/tests/public_api_surface.rs new file mode 100644 index 0000000..c5fb8d6 --- /dev/null +++ b/tests/public_api_surface.rs @@ -0,0 +1,145 @@ +//! Public API contract smoke tests. +//! +//! These tests are intentionally shallow: they verify that the sanctioned +//! public entrypoints remain available and that stable versus experimental +//! namespaces stay explicit. + +use cougr_core::accounts::{ + verify_secp256r1, ClassicAccount, GameAction, Secp256r1Key, Secp256r1Storage, SessionBuilder, +}; +use cougr_core::standards::{ + AccessControl, BatchExecutor, DelayedExecutionPolicy, ExecutionGuard, Ownable, Ownable2Step, + Pausable, RecoveryGuard, StandardsError, +}; +use cougr_core::zk::experimental::{ + bytes32_to_scalar, open_state_channel, u32_to_scalar, CustomCircuit, FogOfWarSnapshot, + GameCircuit, MovementCircuit, RecursiveProofLayout, +}; +use cougr_core::zk::stable::{encode_commit_reveal, CommitReveal, COMMIT_REVEAL_TYPE}; +use cougr_core::{Position, SimpleWorld}; +use soroban_sdk::{symbol_short, testutils::Address as _, Address, Bytes, BytesN, Env, Vec}; + +#[test] +fn sanctioned_root_api_supports_basic_ecs_flow() { + let env = Env::default(); + let mut world = SimpleWorld::new(&env); + let entity = world.spawn_entity(); + + world.set_typed(&env, entity, &Position::new(3, 4)); + + let pos: Position = world.get_typed(&env, entity).unwrap(); + assert_eq!(pos.x, 3); + assert_eq!(pos.y, 4); +} + +#[test] +fn stable_zk_namespace_exposes_stable_commit_reveal_flow() { + let env = Env::default(); + let commitment = BytesN::from_array(&env, &[9u8; 32]); + let encoded = encode_commit_reveal(&env, &commitment, 123, false); + + assert!(!COMMIT_REVEAL_TYPE.is_empty()); + assert_eq!(encoded.len(), 41); + + let _stable_component: Option = None; +} + +#[test] +fn experimental_zk_namespace_exposes_proof_helpers_explicitly() { + let env = Env::default(); + let g1 = cougr_core::zk::G1Point { + bytes: BytesN::from_array(&env, &[0u8; 64]), + }; + let g2 = cougr_core::zk::G2Point { + bytes: BytesN::from_array(&env, &[0u8; 128]), + }; + let mut ic = Vec::new(&env); + for _ in 0..6 { + ic.push_back(g1.clone()); + } + let vk = cougr_core::zk::VerificationKey { + alpha: g1.clone(), + beta: g2.clone(), + gamma: g2.clone(), + delta: g2, + ic, + }; + + let _movement = MovementCircuit::new(vk.clone(), 10); + let _game_circuit: &dyn GameCircuit = &_movement; + let _custom = CustomCircuit::new( + vk, + vec![ + u32_to_scalar(&env, 42), + bytes32_to_scalar(&BytesN::from_array(&env, &[1u8; 32])), + ], + ); + let _fog = FogOfWarSnapshot { + map_root: BytesN::from_array(&env, &[2u8; 32]), + explored_root: BytesN::from_array(&env, &[3u8; 32]), + origin_x: 0, + origin_y: 0, + visibility_radius: 3, + }; + let _channel = open_state_channel( + BytesN::from_array(&env, &[4u8; 32]), + BytesN::from_array(&env, &[5u8; 32]), + BytesN::from_array(&env, &[6u8; 32]), + 10, + ) + .unwrap(); + let _layout = RecursiveProofLayout::from_step_roots( + &env, + BytesN::from_array(&env, &[7u8; 32]), + BytesN::from_array(&env, &[8u8; 32]), + &[BytesN::from_array(&env, &[9u8; 32])], + ) + .unwrap(); +} + +#[test] +fn accounts_namespace_exposes_curated_beta_entrypoints() { + let env = Env::default(); + let account = Address::generate(&env); + let _classic = ClassicAccount::new(account.clone()); + + let _action = GameAction { + system_name: symbol_short!("move"), + data: Bytes::new(&env), + }; + + let _session_builder = SessionBuilder::new(&env).allow_action(symbol_short!("move")); + + let key = Secp256r1Key { + public_key: BytesN::from_array(&env, &[4u8; 65]), + label: symbol_short!("passkey"), + registered_at: 0, + }; + let _storage_marker = core::mem::size_of::(); + let _stored_key = key; + + let _verify_fn: fn( + &Env, + &BytesN<65>, + &Bytes, + &BytesN<64>, + ) -> Result<(), cougr_core::accounts::AccountError> = verify_secp256r1; +} + +#[test] +fn standards_namespace_exposes_reusable_contract_primitives() { + let env = Env::default(); + let account = Address::generate(&env); + + let _ownable = Ownable::new(symbol_short!("own")); + let _ownable_2step = Ownable2Step::new(symbol_short!("own2")); + let _access = AccessControl::new(symbol_short!("acl")); + let _pausable = Pausable::new(symbol_short!("pause")); + let _guard = ExecutionGuard::new(symbol_short!("exec")); + let _recovery_guard = RecoveryGuard::new(symbol_short!("reco")); + let _delayed = DelayedExecutionPolicy::new(symbol_short!("delay")); + let _batch = BatchExecutor::new(8); + + let _error_marker = StandardsError::Paused; + let _ = account; +} diff --git a/tests/stress.rs b/tests/stress.rs index f14be92..c618870 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -3,10 +3,7 @@ //! Tests with 50-100+ entities to verify performance and correctness //! under load. -use cougr_core::commands::CommandQueue; -use cougr_core::component::ComponentStorage; -use cougr_core::query::SimpleQueryCache; -use cougr_core::simple_world::SimpleWorld; +use cougr_core::{CommandQueue, ComponentStorage, SimpleQueryCache, SimpleWorld}; use soroban_sdk::{symbol_short, Bytes, Env, Symbol}; // ---------------------------------------------------------------------------

` -unsafe impl> SystemParamBuilder> - for WhenBuilder -{ - fn build(self, world: &mut World) -> as SystemParam>::State { - self.0.build(world) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - entity::Entities, - error::Result, - prelude::{Component, Query}, - reflect::ReflectResource, - system::{Local, RunSystemOnce}, - }; - use alloc::vec; - use bevy_reflect::{FromType, Reflect, ReflectRef}; - - use super::*; - - #[derive(Component)] - struct A; - - #[derive(Component)] - struct B; - - #[derive(Component)] - struct C; - - #[derive(Resource, Default, Reflect)] - #[reflect(Resource)] - struct R { - foo: usize, - } - - fn local_system(local: Local) -> u64 { - *local - } - - fn query_system(query: Query<()>) -> usize { - query.iter().count() - } - - fn query_system_result(query: Query<()>) -> Result { - Ok(query.iter().count()) - } - - fn multi_param_system(a: Local, b: Local) -> u64 { - *a + *b + 1 - } - - #[test] - fn local_builder() { - let mut world = World::new(); - - let system = (LocalBuilder(10),) - .build_state(&mut world) - .build_system(local_system); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 10); - } - - #[test] - fn query_builder() { - let mut world = World::new(); - - world.spawn(A); - world.spawn_empty(); - - let system = (QueryParamBuilder::new(|query| { - query.with::(); - }),) - .build_state(&mut world) - .build_system(query_system); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 1); - } - - #[test] - fn query_builder_result_fallible() { - let mut world = World::new(); - - world.spawn(A); - world.spawn_empty(); - - let system = (QueryParamBuilder::new(|query| { - query.with::(); - }),) - .build_state(&mut world) - .build_system(query_system_result); - - // The type annotation here is necessary since the system - // could also return `Result` - let output: usize = world.run_system_once(system).unwrap(); - assert_eq!(output, 1); - } - - #[test] - fn query_builder_result_infallible() { - let mut world = World::new(); - - world.spawn(A); - world.spawn_empty(); - - let system = (QueryParamBuilder::new(|query| { - query.with::(); - }),) - .build_state(&mut world) - .build_system(query_system_result); - - // The type annotation here is necessary since the system - // could also return `usize` - let output: Result = world.run_system_once(system).unwrap(); - assert_eq!(output.unwrap(), 1); - } - - #[test] - fn query_builder_state() { - let mut world = World::new(); - - world.spawn(A); - world.spawn_empty(); - - let state = QueryBuilder::new(&mut world).with::().build(); - - let system = (state,).build_state(&mut world).build_system(query_system); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 1); - } - - #[test] - fn multi_param_builder() { - let mut world = World::new(); - - world.spawn(A); - world.spawn_empty(); - - let system = (LocalBuilder(0), ParamBuilder) - .build_state(&mut world) - .build_system(multi_param_system); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 1); - } - - #[test] - fn vec_builder() { - let mut world = World::new(); - - world.spawn((A, B, C)); - world.spawn((A, B)); - world.spawn((A, C)); - world.spawn((A, C)); - world.spawn_empty(); - - let system = (vec![ - QueryParamBuilder::new_box(|builder| { - builder.with::().without::(); - }), - QueryParamBuilder::new_box(|builder| { - builder.with::().without::(); - }), - ],) - .build_state(&mut world) - .build_system(|params: Vec>| { - let mut count: usize = 0; - params - .into_iter() - .for_each(|mut query| count += query.iter_mut().count()); - count - }); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 3); - } - - #[test] - fn multi_param_builder_inference() { - let mut world = World::new(); - - world.spawn(A); - world.spawn_empty(); - - let system = (LocalBuilder(0u64), ParamBuilder::local::()) - .build_state(&mut world) - .build_system(|a, b| *a + *b + 1); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 1); - } - - #[test] - fn param_set_builder() { - let mut world = World::new(); - - world.spawn((A, B, C)); - world.spawn((A, B)); - world.spawn((A, C)); - world.spawn((A, C)); - world.spawn_empty(); - - let system = (ParamSetBuilder(( - QueryParamBuilder::new(|builder| { - builder.with::(); - }), - QueryParamBuilder::new(|builder| { - builder.with::(); - }), - )),) - .build_state(&mut world) - .build_system(|mut params: ParamSet<(Query<&mut A>, Query<&mut A>)>| { - params.p0().iter().count() + params.p1().iter().count() - }); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 5); - } - - #[test] - fn param_set_vec_builder() { - let mut world = World::new(); - - world.spawn((A, B, C)); - world.spawn((A, B)); - world.spawn((A, C)); - world.spawn((A, C)); - world.spawn_empty(); - - let system = (ParamSetBuilder(vec![ - QueryParamBuilder::new_box(|builder| { - builder.with::(); - }), - QueryParamBuilder::new_box(|builder| { - builder.with::(); - }), - ]),) - .build_state(&mut world) - .build_system(|mut params: ParamSet>>| { - let mut count = 0; - params.for_each(|mut query| count += query.iter_mut().count()); - count - }); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 5); - } - - #[test] - fn dyn_builder() { - let mut world = World::new(); - - world.spawn(A); - world.spawn_empty(); - - let system = ( - DynParamBuilder::new(LocalBuilder(3_usize)), - DynParamBuilder::new::>(QueryParamBuilder::new(|builder| { - builder.with::(); - })), - DynParamBuilder::new::<&Entities>(ParamBuilder), - ) - .build_state(&mut world) - .build_system( - |mut p0: DynSystemParam, mut p1: DynSystemParam, mut p2: DynSystemParam| { - let local = *p0.downcast_mut::>().unwrap(); - let query_count = p1.downcast_mut::>().unwrap().iter().count(); - let _entities = p2.downcast_mut::<&Entities>().unwrap(); - assert!(p0.downcast_mut::>().is_none()); - local + query_count - }, - ); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 4); - } - - #[derive(SystemParam)] - #[system_param(builder)] - struct CustomParam<'w, 's> { - query: Query<'w, 's, ()>, - local: Local<'s, usize>, - } - - #[test] - fn custom_param_builder() { - let mut world = World::new(); - - world.spawn(A); - world.spawn_empty(); - - let system = (CustomParamBuilder { - local: LocalBuilder(100), - query: QueryParamBuilder::new(|builder| { - builder.with::(); - }), - },) - .build_state(&mut world) - .build_system(|param: CustomParam| *param.local + param.query.iter().count()); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 101); - } - - #[test] - fn filtered_resource_conflicts_read_with_res() { - let mut world = World::new(); - ( - ParamBuilder::resource(), - FilteredResourcesParamBuilder::new(|builder| { - builder.add_read::(); - }), - ) - .build_state(&mut world) - .build_system(|_r: Res, _fr: FilteredResources| {}); - } - - #[test] - #[should_panic] - fn filtered_resource_conflicts_read_with_resmut() { - let mut world = World::new(); - ( - ParamBuilder::resource_mut(), - FilteredResourcesParamBuilder::new(|builder| { - builder.add_read::(); - }), - ) - .build_state(&mut world) - .build_system(|_r: ResMut, _fr: FilteredResources| {}); - } - - #[test] - #[should_panic] - fn filtered_resource_conflicts_read_all_with_resmut() { - let mut world = World::new(); - ( - ParamBuilder::resource_mut(), - FilteredResourcesParamBuilder::new(|builder| { - builder.add_read_all(); - }), - ) - .build_state(&mut world) - .build_system(|_r: ResMut, _fr: FilteredResources| {}); - } - - #[test] - fn filtered_resource_mut_conflicts_read_with_res() { - let mut world = World::new(); - ( - ParamBuilder::resource(), - FilteredResourcesMutParamBuilder::new(|builder| { - builder.add_read::(); - }), - ) - .build_state(&mut world) - .build_system(|_r: Res, _fr: FilteredResourcesMut| {}); - } - - #[test] - #[should_panic] - fn filtered_resource_mut_conflicts_read_with_resmut() { - let mut world = World::new(); - ( - ParamBuilder::resource_mut(), - FilteredResourcesMutParamBuilder::new(|builder| { - builder.add_read::(); - }), - ) - .build_state(&mut world) - .build_system(|_r: ResMut, _fr: FilteredResourcesMut| {}); - } - - #[test] - #[should_panic] - fn filtered_resource_mut_conflicts_write_with_res() { - let mut world = World::new(); - ( - ParamBuilder::resource(), - FilteredResourcesMutParamBuilder::new(|builder| { - builder.add_write::(); - }), - ) - .build_state(&mut world) - .build_system(|_r: Res, _fr: FilteredResourcesMut| {}); - } - - #[test] - #[should_panic] - fn filtered_resource_mut_conflicts_write_all_with_res() { - let mut world = World::new(); - ( - ParamBuilder::resource(), - FilteredResourcesMutParamBuilder::new(|builder| { - builder.add_write_all(); - }), - ) - .build_state(&mut world) - .build_system(|_r: Res, _fr: FilteredResourcesMut| {}); - } - - #[test] - #[should_panic] - fn filtered_resource_mut_conflicts_write_with_resmut() { - let mut world = World::new(); - ( - ParamBuilder::resource_mut(), - FilteredResourcesMutParamBuilder::new(|builder| { - builder.add_write::(); - }), - ) - .build_state(&mut world) - .build_system(|_r: ResMut, _fr: FilteredResourcesMut| {}); - } - - #[test] - fn filtered_resource_reflect() { - let mut world = World::new(); - world.insert_resource(R { foo: 7 }); - - let system = (FilteredResourcesParamBuilder::new(|builder| { - builder.add_read::(); - }),) - .build_state(&mut world) - .build_system(|res: FilteredResources| { - let reflect_resource = >::from_type(); - let ReflectRef::Struct(reflect_struct) = - reflect_resource.reflect(res).unwrap().reflect_ref() - else { - panic!() - }; - *reflect_struct - .field("foo") - .unwrap() - .try_downcast_ref::() - .unwrap() - }); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 7); - } -} diff --git a/src/system/combinator.rs b/src/system/combinator.rs deleted file mode 100644 index 1fc69d1..0000000 --- a/src/system/combinator.rs +++ /dev/null @@ -1,463 +0,0 @@ -use alloc::{format, vec::Vec}; -use bevy_utils::prelude::DebugName; -use core::marker::PhantomData; - -use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, - prelude::World, - query::FilteredAccessSet, - schedule::InternedSystemSet, - system::{input::SystemInput, SystemIn, SystemParamValidationError}, - world::unsafe_world_cell::UnsafeWorldCell, -}; - -use super::{IntoSystem, ReadOnlySystem, RunSystemError, System}; - -/// Customizes the behavior of a [`CombinatorSystem`]. -/// -/// # Examples -/// -/// ``` -/// use bevy_ecs::prelude::*; -/// use bevy_ecs::system::{CombinatorSystem, Combine, RunSystemError}; -/// -/// // A system combinator that performs an exclusive-or (XOR) -/// // operation on the output of two systems. -/// pub type Xor = CombinatorSystem; -/// -/// // This struct is used to customize the behavior of our combinator. -/// pub struct XorMarker; -/// -/// impl Combine for XorMarker -/// where -/// A: System, -/// B: System, -/// { -/// type In = (); -/// type Out = bool; -/// -/// fn combine( -/// _input: Self::In, -/// a: impl FnOnce(A::In) -> Result, -/// b: impl FnOnce(B::In) -> Result, -/// ) -> Result { -/// Ok(a(())? ^ b(())?) -/// } -/// } -/// -/// # #[derive(Resource, PartialEq, Eq)] struct A(u32); -/// # #[derive(Resource, PartialEq, Eq)] struct B(u32); -/// # #[derive(Resource, Default)] struct RanFlag(bool); -/// # let mut world = World::new(); -/// # world.init_resource::(); -/// # -/// # let mut app = Schedule::default(); -/// app.add_systems(my_system.run_if(Xor::new( -/// IntoSystem::into_system(resource_equals(A(1))), -/// IntoSystem::into_system(resource_equals(B(1))), -/// // The name of the combined system. -/// "a ^ b".into(), -/// ))); -/// # fn my_system(mut flag: ResMut) { flag.0 = true; } -/// # -/// # world.insert_resource(A(0)); -/// # world.insert_resource(B(0)); -/// # app.run(&mut world); -/// # // Neither condition passes, so the system does not run. -/// # assert!(!world.resource::().0); -/// # -/// # world.insert_resource(A(1)); -/// # app.run(&mut world); -/// # // Only the first condition passes, so the system runs. -/// # assert!(world.resource::().0); -/// # world.resource_mut::().0 = false; -/// # -/// # world.insert_resource(B(1)); -/// # app.run(&mut world); -/// # // Both conditions pass, so the system does not run. -/// # assert!(!world.resource::().0); -/// # -/// # world.insert_resource(A(0)); -/// # app.run(&mut world); -/// # // Only the second condition passes, so the system runs. -/// # assert!(world.resource::().0); -/// # world.resource_mut::().0 = false; -/// ``` -#[diagnostic::on_unimplemented( - message = "`{Self}` can not combine systems `{A}` and `{B}`", - label = "invalid system combination", - note = "the inputs and outputs of `{A}` and `{B}` are not compatible with this combiner" -)] -pub trait Combine { - /// The [input](System::In) type for a [`CombinatorSystem`]. - type In: SystemInput; - - /// The [output](System::Out) type for a [`CombinatorSystem`]. - type Out; - - /// When used in a [`CombinatorSystem`], this function customizes how - /// the two composite systems are invoked and their outputs are combined. - /// - /// See the trait-level docs for [`Combine`] for an example implementation. - fn combine( - input: ::Inner<'_>, - a: impl FnOnce(SystemIn<'_, A>) -> Result, - b: impl FnOnce(SystemIn<'_, B>) -> Result, - ) -> Result; -} - -/// A [`System`] defined by combining two other systems. -/// The behavior of this combinator is specified by implementing the [`Combine`] trait. -/// For a full usage example, see the docs for [`Combine`]. -pub struct CombinatorSystem { - _marker: PhantomData Func>, - a: A, - b: B, - name: DebugName, -} - -impl CombinatorSystem { - /// Creates a new system that combines two inner systems. - /// - /// The returned system will only be usable if `Func` implements [`Combine`]. - pub fn new(a: A, b: B, name: DebugName) -> Self { - Self { - _marker: PhantomData, - a, - b, - name, - } - } -} - -impl System for CombinatorSystem -where - Func: Combine + 'static, - A: System, - B: System, -{ - type In = Func::In; - type Out = Func::Out; - - fn name(&self) -> DebugName { - self.name.clone() - } - - #[inline] - fn flags(&self) -> super::SystemStateFlags { - self.a.flags() | self.b.flags() - } - - unsafe fn run_unsafe( - &mut self, - input: SystemIn<'_, Self>, - world: UnsafeWorldCell, - ) -> Result { - Func::combine( - input, - // SAFETY: The world accesses for both underlying systems have been registered, - // so the caller will guarantee that no other systems will conflict with `a` or `b`. - // If either system has `is_exclusive()`, then the combined system also has `is_exclusive`. - // Since these closures are `!Send + !Sync + !'static`, they can never be called - // in parallel, so their world accesses will not conflict with each other. - |input| unsafe { self.a.run_unsafe(input, world) }, - // `Self::validate_param_unsafe` already validated the first system, - // but we still need to validate the second system once the first one runs. - // SAFETY: See the comment above. - |input| unsafe { - self.b.validate_param_unsafe(world)?; - self.b.run_unsafe(input, world) - }, - ) - } - - #[cfg(feature = "hotpatching")] - #[inline] - fn refresh_hotpatch(&mut self) { - self.a.refresh_hotpatch(); - self.b.refresh_hotpatch(); - } - - #[inline] - fn apply_deferred(&mut self, world: &mut World) { - self.a.apply_deferred(world); - self.b.apply_deferred(world); - } - - #[inline] - fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) { - self.a.queue_deferred(world.reborrow()); - self.b.queue_deferred(world); - } - - #[inline] - unsafe fn validate_param_unsafe( - &mut self, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // We only validate parameters for the first system, - // since it may make changes to the world that affect - // whether the second system has valid parameters. - // The second system will be validated in `Self::run_unsafe`. - // SAFETY: Delegate to other `System` implementations. - unsafe { self.a.validate_param_unsafe(world) } - } - - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { - let mut a_access = self.a.initialize(world); - let b_access = self.b.initialize(world); - a_access.extend(b_access); - a_access - } - - fn check_change_tick(&mut self, check: CheckChangeTicks) { - self.a.check_change_tick(check); - self.b.check_change_tick(check); - } - - fn default_system_sets(&self) -> Vec { - let mut default_sets = self.a.default_system_sets(); - default_sets.append(&mut self.b.default_system_sets()); - default_sets - } - - fn get_last_run(&self) -> Tick { - self.a.get_last_run() - } - - fn set_last_run(&mut self, last_run: Tick) { - self.a.set_last_run(last_run); - self.b.set_last_run(last_run); - } -} - -/// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world. -unsafe impl ReadOnlySystem for CombinatorSystem -where - Func: Combine + 'static, - A: ReadOnlySystem, - B: ReadOnlySystem, -{ -} - -impl Clone for CombinatorSystem -where - A: Clone, - B: Clone, -{ - /// Clone the combined system. The cloned instance must be `.initialize()`d before it can run. - fn clone(&self) -> Self { - CombinatorSystem::new(self.a.clone(), self.b.clone(), self.name.clone()) - } -} - -/// An [`IntoSystem`] creating an instance of [`PipeSystem`]. -pub struct IntoPipeSystem { - a: A, - b: B, -} - -impl IntoPipeSystem { - /// Creates a new [`IntoSystem`] that pipes two inner systems. - pub const fn new(a: A, b: B) -> Self { - Self { a, b } - } -} - -#[doc(hidden)] -pub struct IsPipeSystemMarker; - -impl IntoSystem - for IntoPipeSystem -where - IA: SystemInput, - A: IntoSystem, - B: IntoSystem, - for<'a> IB: SystemInput = OA>, -{ - type System = PipeSystem; - - fn into_system(this: Self) -> Self::System { - let system_a = IntoSystem::into_system(this.a); - let system_b = IntoSystem::into_system(this.b); - let name = format!("Pipe({}, {})", system_a.name(), system_b.name()); - PipeSystem::new(system_a, system_b, DebugName::owned(name)) - } -} - -/// A [`System`] created by piping the output of the first system into the input of the second. -/// -/// This can be repeated indefinitely, but system pipes cannot branch: the output is consumed by the receiving system. -/// -/// Given two systems `A` and `B`, A may be piped into `B` as `A.pipe(B)` if the output type of `A` is -/// equal to the input type of `B`. -/// -/// Note that for [`FunctionSystem`](crate::system::FunctionSystem)s the output is the return value -/// of the function and the input is the first [`SystemParam`](crate::system::SystemParam) if it is -/// tagged with [`In`](crate::system::In) or `()` if the function has no designated input parameter. -/// -/// # Examples -/// -/// ``` -/// use std::num::ParseIntError; -/// -/// use bevy_ecs::prelude::*; -/// -/// fn main() { -/// let mut world = World::default(); -/// world.insert_resource(Message("42".to_string())); -/// -/// // pipe the `parse_message_system`'s output into the `filter_system`s input -/// let mut piped_system = IntoSystem::into_system(parse_message_system.pipe(filter_system)); -/// piped_system.initialize(&mut world); -/// assert_eq!(piped_system.run((), &mut world).unwrap(), Some(42)); -/// } -/// -/// #[derive(Resource)] -/// struct Message(String); -/// -/// fn parse_message_system(message: Res) -> Result { -/// message.0.parse::() -/// } -/// -/// fn filter_system(In(result): In>) -> Option { -/// result.ok().filter(|&n| n < 100) -/// } -/// ``` -pub struct PipeSystem { - a: A, - b: B, - name: DebugName, -} - -impl PipeSystem -where - A: System, - B: System, - for<'a> B::In: SystemInput = A::Out>, -{ - /// Creates a new system that pipes two inner systems. - pub fn new(a: A, b: B, name: DebugName) -> Self { - Self { a, b, name } - } -} - -impl System for PipeSystem -where - A: System, - B: System, - for<'a> B::In: SystemInput = A::Out>, -{ - type In = A::In; - type Out = B::Out; - - fn name(&self) -> DebugName { - self.name.clone() - } - - #[inline] - fn flags(&self) -> super::SystemStateFlags { - self.a.flags() | self.b.flags() - } - - unsafe fn run_unsafe( - &mut self, - input: SystemIn<'_, Self>, - world: UnsafeWorldCell, - ) -> Result { - let value = self.a.run_unsafe(input, world)?; - // `Self::validate_param_unsafe` already validated the first system, - // but we still need to validate the second system once the first one runs. - self.b.validate_param_unsafe(world)?; - self.b.run_unsafe(value, world) - } - - #[cfg(feature = "hotpatching")] - #[inline] - fn refresh_hotpatch(&mut self) { - self.a.refresh_hotpatch(); - self.b.refresh_hotpatch(); - } - - fn apply_deferred(&mut self, world: &mut World) { - self.a.apply_deferred(world); - self.b.apply_deferred(world); - } - - fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) { - self.a.queue_deferred(world.reborrow()); - self.b.queue_deferred(world); - } - - unsafe fn validate_param_unsafe( - &mut self, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // We only validate parameters for the first system, - // since it may make changes to the world that affect - // whether the second system has valid parameters. - // The second system will be validated in `Self::run_unsafe`. - // SAFETY: Delegate to the `System` implementation for `a`. - unsafe { self.a.validate_param_unsafe(world) } - } - - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { - let mut a_access = self.a.initialize(world); - let b_access = self.b.initialize(world); - a_access.extend(b_access); - a_access - } - - fn check_change_tick(&mut self, check: CheckChangeTicks) { - self.a.check_change_tick(check); - self.b.check_change_tick(check); - } - - fn default_system_sets(&self) -> Vec { - let mut default_sets = self.a.default_system_sets(); - default_sets.append(&mut self.b.default_system_sets()); - default_sets - } - - fn get_last_run(&self) -> Tick { - self.a.get_last_run() - } - - fn set_last_run(&mut self, last_run: Tick) { - self.a.set_last_run(last_run); - self.b.set_last_run(last_run); - } -} - -/// SAFETY: Both systems are read-only, so any system created by piping them will only read from the world. -unsafe impl ReadOnlySystem for PipeSystem -where - A: ReadOnlySystem, - B: ReadOnlySystem, - for<'a> B::In: SystemInput = A::Out>, -{ -} - -#[cfg(test)] -mod tests { - - #[test] - fn exclusive_system_piping_is_possible() { - use crate::prelude::*; - - fn my_exclusive_system(_world: &mut World) -> u32 { - 1 - } - - fn out_pipe(input: In) { - assert!(input.0 == 1); - } - - let mut world = World::new(); - - let mut schedule = Schedule::default(); - schedule.add_systems(my_exclusive_system.pipe(out_pipe)); - - schedule.run(&mut world); - } -} diff --git a/src/system/exclusive_function_system.rs b/src/system/exclusive_function_system.rs deleted file mode 100644 index 241f995..0000000 --- a/src/system/exclusive_function_system.rs +++ /dev/null @@ -1,362 +0,0 @@ -use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, - error::Result, - query::FilteredAccessSet, - schedule::{InternedSystemSet, SystemSet}, - system::{ - check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, IntoResult, - IntoSystem, System, SystemIn, SystemInput, SystemMeta, - }, - world::{unsafe_world_cell::UnsafeWorldCell, World}, -}; - -use alloc::{borrow::Cow, vec, vec::Vec}; -use bevy_utils::prelude::DebugName; -use core::marker::PhantomData; -use variadics_please::all_tuples; - -use super::{RunSystemError, SystemParamValidationError, SystemStateFlags}; - -/// A function system that runs with exclusive [`World`] access. -/// -/// You get this by calling [`IntoSystem::into_system`] on a function that only accepts -/// [`ExclusiveSystemParam`]s. -/// -/// [`ExclusiveFunctionSystem`] must be `.initialized` before they can be run. -pub struct ExclusiveFunctionSystem -where - F: ExclusiveSystemParamFunction, -{ - func: F, - #[cfg(feature = "hotpatching")] - current_ptr: subsecond::HotFnPtr, - param_state: Option<::State>, - system_meta: SystemMeta, - // NOTE: PhantomData T> gives this safe Send/Sync impls - marker: PhantomData (Marker, Out)>, -} - -impl ExclusiveFunctionSystem -where - F: ExclusiveSystemParamFunction, -{ - /// Return this system with a new name. - /// - /// Useful to give closure systems more readable and unique names for debugging and tracing. - pub fn with_name(mut self, new_name: impl Into>) -> Self { - self.system_meta.set_name(new_name); - self - } -} - -/// A marker type used to distinguish exclusive function systems from regular function systems. -#[doc(hidden)] -pub struct IsExclusiveFunctionSystem; - -impl IntoSystem for F -where - Out: 'static, - Marker: 'static, - F::Out: IntoResult, - F: ExclusiveSystemParamFunction, -{ - type System = ExclusiveFunctionSystem; - fn into_system(func: Self) -> Self::System { - ExclusiveFunctionSystem { - func, - #[cfg(feature = "hotpatching")] - current_ptr: subsecond::HotFn::current( - >::run, - ) - .ptr_address(), - param_state: None, - system_meta: SystemMeta::new::(), - marker: PhantomData, - } - } -} - -const PARAM_MESSAGE: &str = "System's param_state was not found. Did you forget to initialize this system before running it?"; - -impl System for ExclusiveFunctionSystem -where - Marker: 'static, - Out: 'static, - F::Out: IntoResult, - F: ExclusiveSystemParamFunction, -{ - type In = F::In; - type Out = Out; - - #[inline] - fn name(&self) -> DebugName { - self.system_meta.name.clone() - } - - #[inline] - fn flags(&self) -> SystemStateFlags { - // non-send , exclusive , no deferred - // the executor runs exclusive systems on the main thread, so this - // field reflects that constraint - // exclusive systems have no deferred system params - SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE - } - - #[inline] - unsafe fn run_unsafe( - &mut self, - input: SystemIn<'_, Self>, - world: UnsafeWorldCell, - ) -> Result { - // SAFETY: The safety is upheld by the caller. - let world = unsafe { world.world_mut() }; - world.last_change_tick_scope(self.system_meta.last_run, |world| { - #[cfg(feature = "trace")] - let _span_guard = self.system_meta.system_span.enter(); - - let params = F::Param::get_param( - self.param_state.as_mut().expect(PARAM_MESSAGE), - &self.system_meta, - ); - - #[cfg(feature = "hotpatching")] - let out = { - let mut hot_fn = - subsecond::HotFn::current(>::run); - // SAFETY: - // - pointer used to call is from the current jump table - unsafe { - hot_fn - .try_call_with_ptr(self.current_ptr, (&mut self.func, world, input, params)) - .expect("Error calling hotpatched system. Run a full rebuild") - } - }; - #[cfg(not(feature = "hotpatching"))] - let out = self.func.run(world, input, params); - - world.flush(); - self.system_meta.last_run = world.increment_change_tick(); - - IntoResult::into_result(out) - }) - } - - #[cfg(feature = "hotpatching")] - #[inline] - fn refresh_hotpatch(&mut self) { - let new = subsecond::HotFn::current(>::run) - .ptr_address(); - if new != self.current_ptr { - log::debug!("system {} hotpatched", self.name()); - } - self.current_ptr = new; - } - - #[inline] - fn apply_deferred(&mut self, _world: &mut World) { - // "pure" exclusive systems do not have any buffers to apply. - // Systems made by piping a normal system with an exclusive system - // might have buffers to apply, but this is handled by `PipeSystem`. - } - - #[inline] - fn queue_deferred(&mut self, _world: crate::world::DeferredWorld) { - // "pure" exclusive systems do not have any buffers to apply. - // Systems made by piping a normal system with an exclusive system - // might have buffers to apply, but this is handled by `PipeSystem`. - } - - #[inline] - unsafe fn validate_param_unsafe( - &mut self, - _world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // All exclusive system params are always available. - Ok(()) - } - - #[inline] - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { - self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX); - self.param_state = Some(F::Param::init(world, &mut self.system_meta)); - FilteredAccessSet::new() - } - - #[inline] - fn check_change_tick(&mut self, check: CheckChangeTicks) { - check_system_change_tick( - &mut self.system_meta.last_run, - check, - self.system_meta.name.clone(), - ); - } - - fn default_system_sets(&self) -> Vec { - let set = crate::schedule::SystemTypeSet::::new(); - vec![set.intern()] - } - - fn get_last_run(&self) -> Tick { - self.system_meta.last_run - } - - fn set_last_run(&mut self, last_run: Tick) { - self.system_meta.last_run = last_run; - } -} - -/// A trait implemented for all exclusive system functions that can be used as [`System`]s. -/// -/// This trait can be useful for making your own systems which accept other systems, -/// sometimes called higher order systems. -#[diagnostic::on_unimplemented( - message = "`{Self}` is not an exclusive system", - label = "invalid system" -)] -pub trait ExclusiveSystemParamFunction: Send + Sync + 'static { - /// The input type to this system. See [`System::In`]. - type In: SystemInput; - - /// The return type of this system. See [`System::Out`]. - type Out; - - /// The [`ExclusiveSystemParam`]'s defined by this system's `fn` parameters. - type Param: ExclusiveSystemParam; - - /// Executes this system once. See [`System::run`]. - fn run( - &mut self, - world: &mut World, - input: ::Inner<'_>, - param_value: ExclusiveSystemParamItem, - ) -> Self::Out; -} - -/// A marker type used to distinguish exclusive function systems with and without input. -#[doc(hidden)] -pub struct HasExclusiveSystemInput; - -macro_rules! impl_exclusive_system_function { - ($($param: ident),*) => { - #[expect( - clippy::allow_attributes, - reason = "This is within a macro, and as such, the below lints may not always apply." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - impl ExclusiveSystemParamFunction Out> for Func - where - Func: Send + Sync + 'static, - for <'a> &'a mut Func: - FnMut(&mut World, $($param),*) -> Out + - FnMut(&mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out, - Out: 'static, - { - type In = (); - type Out = Out; - type Param = ($($param,)*); - #[inline] - fn run(&mut self, world: &mut World, _in: (), param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out { - // Yes, this is strange, but `rustc` fails to compile this impl - // without using this function. It fails to recognize that `func` - // is a function, potentially because of the multiple impls of `FnMut` - fn call_inner( - mut f: impl FnMut(&mut World, $($param,)*) -> Out, - world: &mut World, - $($param: $param,)* - ) -> Out { - f(world, $($param,)*) - } - let ($($param,)*) = param_value; - call_inner(self, world, $($param),*) - } - } - - #[expect( - clippy::allow_attributes, - reason = "This is within a macro, and as such, the below lints may not always apply." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - impl ExclusiveSystemParamFunction<(HasExclusiveSystemInput, fn(In, $($param,)*) -> Out)> for Func - where - Func: Send + Sync + 'static, - for <'a> &'a mut Func: - FnMut(In, &mut World, $($param),*) -> Out + - FnMut(In::Param<'_>, &mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out, - In: SystemInput + 'static, - Out: 'static, - { - type In = In; - type Out = Out; - type Param = ($($param,)*); - #[inline] - fn run(&mut self, world: &mut World, input: In::Inner<'_>, param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out { - // Yes, this is strange, but `rustc` fails to compile this impl - // without using this function. It fails to recognize that `func` - // is a function, potentially because of the multiple impls of `FnMut` - fn call_inner( - _: PhantomData, - mut f: impl FnMut(In::Param<'_>, &mut World, $($param,)*) -> Out, - input: In::Inner<'_>, - world: &mut World, - $($param: $param,)* - ) -> Out { - f(In::wrap(input), world, $($param,)*) - } - let ($($param,)*) = param_value; - call_inner(PhantomData::, self, input, world, $($param),*) - } - } - }; -} -// Note that we rely on the highest impl to be <= the highest order of the tuple impls -// of `SystemParam` created. -all_tuples!(impl_exclusive_system_function, 0, 16, F); - -#[cfg(test)] -mod tests { - use crate::system::input::SystemInput; - - use super::*; - - #[test] - fn into_system_type_id_consistency() { - fn test(function: T) - where - T: IntoSystem + Copy, - { - fn reference_system(_world: &mut World) {} - - use core::any::TypeId; - - let system = IntoSystem::into_system(function); - - assert_eq!( - system.type_id(), - function.system_type_id(), - "System::type_id should be consistent with IntoSystem::system_type_id" - ); - - assert_eq!( - system.type_id(), - TypeId::of::(), - "System::type_id should be consistent with TypeId::of::()" - ); - - assert_ne!( - system.type_id(), - IntoSystem::into_system(reference_system).type_id(), - "Different systems should have different TypeIds" - ); - } - - fn exclusive_function_system(_world: &mut World) {} - - test(exclusive_function_system); - } -} diff --git a/src/system/exclusive_system_param.rs b/src/system/exclusive_system_param.rs deleted file mode 100644 index f87182a..0000000 --- a/src/system/exclusive_system_param.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::{ - prelude::{FromWorld, QueryState}, - query::{QueryData, QueryFilter}, - system::{Local, SystemMeta, SystemParam, SystemState}, - world::World, -}; -use bevy_platform::cell::SyncCell; -use core::marker::PhantomData; -use variadics_please::all_tuples; - -/// A parameter that can be used in an exclusive system (a system with an `&mut World` parameter). -/// Any parameters implementing this trait must come after the `&mut World` parameter. -#[diagnostic::on_unimplemented( - message = "`{Self}` can not be used as a parameter for an exclusive system", - label = "invalid system parameter" -)] -pub trait ExclusiveSystemParam: Sized { - /// Used to store data which persists across invocations of a system. - type State: Send + Sync + 'static; - /// The item type returned when constructing this system param. - /// See [`SystemParam::Item`]. - type Item<'s>: ExclusiveSystemParam; - - /// Creates a new instance of this param's [`State`](Self::State). - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self::State; - - /// Creates a parameter to be passed into an [`ExclusiveSystemParamFunction`]. - /// - /// [`ExclusiveSystemParamFunction`]: super::ExclusiveSystemParamFunction - fn get_param<'s>(state: &'s mut Self::State, system_meta: &SystemMeta) -> Self::Item<'s>; -} - -/// Shorthand way of accessing the associated type [`ExclusiveSystemParam::Item`] -/// for a given [`ExclusiveSystemParam`]. -pub type ExclusiveSystemParamItem<'s, P> =