├── .config └── nextest.toml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml └── workflows │ ├── ci.yml │ ├── deny.yml │ └── website.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches ├── benches_main.rs ├── big_or_small.rs ├── parallel.rs ├── storage_cmp.rs ├── storage_sparse.rs └── world.rs ├── clippy.toml ├── deny.toml ├── docs ├── tutorials │ ├── book.toml │ └── src │ │ ├── 01_intro.md │ │ ├── 02_hello_world.md │ │ ├── 03_dispatcher.md │ │ ├── 04_resources.md │ │ ├── 05_storages.md │ │ ├── 06_system_data.md │ │ ├── 07_setup.md │ │ ├── 08_join.md │ │ ├── 09_parallel_join.md │ │ ├── 10_rendering.md │ │ ├── 11_advanced_component.md │ │ ├── 12_tracked.md │ │ ├── 13_saveload.md │ │ ├── 14_troubleshooting.md │ │ ├── SUMMARY.md │ │ └── images │ │ ├── component-tables.svg │ │ ├── entity-component.svg │ │ └── system.svg └── website │ ├── config.toml │ ├── content │ ├── _index.md │ └── pages │ │ └── docs.md │ └── themes │ └── hyde │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── config.toml │ ├── sass │ ├── hyde.scss │ ├── poole.scss │ └── print.scss │ ├── static │ └── .gitkeep │ ├── templates │ ├── 404.html │ ├── index.html │ ├── page-nodate.html │ └── section-nodate.html │ └── theme.toml ├── examples ├── async.rs ├── basic.rs ├── bitset.rs ├── cluster_bomb.rs ├── full.rs ├── lend_join.rs ├── ordered_track.rs ├── saveload.rs ├── slices.rs └── track.rs ├── miri.sh ├── specs-derive ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── impl_saveload.rs │ └── lib.rs ├── src ├── bitset.rs ├── changeset.rs ├── error.rs ├── join │ ├── bit_and.rs │ ├── lend_join.rs │ ├── maybe.rs │ ├── mod.rs │ └── par_join.rs ├── lib.rs ├── prelude.rs ├── saveload │ ├── de.rs │ ├── marker.rs │ ├── mod.rs │ ├── ser.rs │ ├── tests.rs │ └── uuid.rs ├── storage │ ├── data.rs │ ├── deref_flagged.rs │ ├── drain.rs │ ├── entry.rs │ ├── flagged.rs │ ├── generic.rs │ ├── mod.rs │ ├── restrict.rs │ ├── storages.rs │ ├── sync_unsafe_cell.rs │ ├── tests.rs │ └── track.rs └── world │ ├── comp.rs │ ├── entity.rs │ ├── lazy.rs │ ├── mod.rs │ ├── tests.rs │ └── world_ext.rs └── tests ├── no_parallel.rs ├── saveload.rs └── tests.rs /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | [profile.default-miri] 2 | slow-timeout = { period = "30s", terminate-after = 1 } 3 | fail-fast = false 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ## Meta 15 | 16 | Rust version: 17 | Specs version / commit: 18 | Operating system: 19 | 20 | ## Reproduction 21 | 22 | Steps to reproduce the behavior: 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 3. Scroll down to '....' 26 | 4. See error 27 | 28 | ## Expected behavior 29 | 30 | A clear and concise description of what you expected to happen. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature-request 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | 12 | ## Motivation 13 | 14 | ## Drawbacks 15 | 16 | * Is it a breaking change? 17 | * Can it impact performance, learnability, etc? 18 | 19 | ## Unresolved questions 20 | 21 | --- 22 | 23 | Please indicate here if you'd like to work on this ticket once it's been approved. Feel free to delete this section if not. 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ## Checklist 8 | 9 | * [ ] I've added tests for all code changes and additions (where applicable) 10 | * [ ] I've added a demonstration of the new feature to one or more examples 11 | * [ ] I've updated the book to reflect my changes 12 | * [ ] Usage of new public items is shown in the API docs 13 | 14 | ## API changes 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - bug 8 | - soundness 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed in 7 days if no further activity occurs. 15 | Feel free to comment if this is still relevant. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | # bors needs CI to trigger for pushes to its staging/trying branches 9 | - staging 10 | - trying 11 | pull_request: 12 | 13 | env: 14 | RUSTFLAGS: -Cdebuginfo=0 15 | CARGO_TERM_COLOR: always 16 | CARGO_INCREMENTAL: 0 17 | RUST_BACKTRACE: 1 18 | 19 | jobs: 20 | tests: 21 | name: Tests 22 | runs-on: ${{ matrix.os }} 23 | continue-on-error: ${{ matrix.toolchain == 'nightly' }} 24 | strategy: 25 | fail-fast: true 26 | matrix: 27 | os: [macos-latest, windows-latest, ubuntu-latest] 28 | toolchain: [stable, beta, nightly, 1.70.0] 29 | steps: 30 | - uses: actions/checkout@v3 31 | 32 | # install the toolchain we are going to compile and test with 33 | - name: install ${{ matrix.toolchain }} toolchain 34 | id: install_toolchain 35 | uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: ${{ matrix.toolchain }} 38 | profile: minimal 39 | override: true 40 | 41 | # we want to install the latest nightly with clippy and rustfmt to run checks against stable 42 | - name: install nightly toolchain 43 | id: install_nightly_toolchain 44 | uses: actions-rs/toolchain@v1 45 | with: 46 | toolchain: nightly 47 | profile: minimal 48 | components: clippy, rustfmt 49 | if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' 50 | 51 | # run rustfmt and clippy checks, but only once 52 | - run: cargo +nightly fmt --all -- --check 53 | if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' 54 | 55 | #- run: cargo +nightly clippy -Z unstable-options --workspace --all-targets --all-features 56 | # if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' 57 | 58 | # run tests 59 | - name: install cargo-hack 60 | uses: taiki-e/install-action@cargo-hack 61 | - run: cargo hack test --workspace --each-feature 62 | if: matrix.toolchain == 'nightly' 63 | - run: cargo hack test --workspace --each-feature --skip nightly 64 | if: matrix.toolchain != 'nightly' 65 | 66 | # build book 67 | # - uses: peaceiris/actions-mdbook@v1 68 | # with: 69 | # mdbook-version: 'latest' 70 | # if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' 71 | 72 | # - run: cargo install mdbook-linkcheck 73 | # if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' 74 | 75 | # - run: mdbook build docs/book 76 | # if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' 77 | 78 | # - run: mdbook test -L ./target/debug/deps docs/book 79 | # if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' 80 | 81 | miri: 82 | name: "Miri" 83 | runs-on: ubuntu-latest 84 | steps: 85 | - uses: actions/checkout@v3 86 | - name: Install Miri 87 | run: | 88 | rustup toolchain install nightly --component miri 89 | rustup override set nightly 90 | cargo miri setup 91 | - name: Install latest nextest release 92 | uses: taiki-e/install-action@nextest 93 | - name: Test with Miri 94 | run: ./miri.sh 95 | -------------------------------------------------------------------------------- /.github/workflows/deny.yml: -------------------------------------------------------------------------------- 1 | name: cargo-deny 2 | 3 | on: [pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | cargo-deny: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | checks: 14 | - advisories 15 | - bans licenses sources 16 | 17 | # Prevent sudden announcement of a new advisory from failing ci: 18 | continue-on-error: ${{ matrix.checks == 'advisories' }} 19 | 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: EmbarkStudios/cargo-deny-action@v1 24 | with: 25 | command: check ${{ matrix.checks }} 26 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Website 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | env: 10 | RUSTFLAGS: -Cdebuginfo=0 11 | CARGO_TERM_COLOR: always 12 | CARGO_INCREMENTAL: 0 13 | RUST_BACKTRACE: 1 14 | 15 | jobs: 16 | Tutorials: 17 | name: Doc Tutorials 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | sparse-checkout: docs/tutorials 23 | 24 | - name: Build Books 25 | uses: taiki-e/install-action@v2 26 | with: 27 | tool: mdbook@0.4.37 28 | 29 | - run: mdbook build docs/tutorials --dest-dir ${GITHUB_WORKSPACE}/public/docs/tutorials 30 | 31 | - uses: actions/upload-artifact@v4 32 | with: 33 | name: Doc Tutorials 34 | path: public/docs/tutorials 35 | 36 | Rust-API: 37 | name: Doc Rust API 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v4 41 | 42 | - uses: actions-rs/toolchain@v1 43 | with: 44 | toolchain: stable 45 | profile: minimal 46 | override: true 47 | components: rust-docs 48 | 49 | - name: Build API Doc 50 | env: 51 | FEATURES: parallel serde derive uuid_entity storage-event-control 52 | run: cargo doc --all --features "${FEATURES}" # --no-deps 53 | 54 | - uses: actions/upload-artifact@v4 55 | with: 56 | name: Doc Rust API 57 | path: | 58 | target/doc 59 | 60 | Website: 61 | name: Doc Website 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v4 65 | with: 66 | sparse-checkout: | 67 | docs/website 68 | 69 | - name: Find Base Url 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | # Use the API to retrieve the github pages url and set an environment variable containing the value. 73 | run: echo "GITHUB_PAGES_URL=$(gh api "repos/$GITHUB_REPOSITORY/pages" --jq '.html_url')" >> $GITHUB_ENV 74 | 75 | - uses: taiki-e/install-action@v2 76 | with: 77 | tool: zola@0.18.0 78 | 79 | - run: zola build --base-url $GITHUB_PAGES_URL 80 | working-directory: docs/website 81 | 82 | - uses: actions/upload-artifact@v4 83 | with: 84 | name: Doc Zola 85 | path: docs/website/public 86 | 87 | Deploy: 88 | name: Deploy 89 | runs-on: ubuntu-latest 90 | needs: [ Tutorials, Rust-API, Website ] 91 | permissions: 92 | pages: write 93 | id-token: write 94 | environment: 95 | name: github-pages 96 | url: ${{ steps.deployment.outputs.page_url }} 97 | if: github.event_name != 'pull_request' 98 | steps: 99 | - uses: actions/download-artifact@v4 100 | with: 101 | name: Doc Tutorials 102 | path: public/docs/tutorials 103 | 104 | - uses: actions/download-artifact@v4 105 | with: 106 | name: Doc Rust API 107 | path: public/docs/api 108 | 109 | - uses: actions/download-artifact@v4 110 | with: 111 | name: Doc Zola 112 | path: public 113 | 114 | - uses: actions/upload-pages-artifact@v3 115 | with: 116 | path: public 117 | 118 | - name: Deploy to GitHub Pages 119 | id: deployment 120 | uses: actions/deploy-pages@v4 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | /target/ 3 | Cargo.lock 4 | .criterion 5 | 6 | # Generated by mdbook 7 | /book/book/ 8 | 9 | # Generated by rustfmt 10 | *.bk 11 | 12 | # IDEs / Editor 13 | *.iml 14 | .idea 15 | 16 | # Website content 17 | /bin 18 | /public 19 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = false 2 | reorder_impl_items = true 3 | use_field_init_shorthand = true 4 | use_try_shorthand = true 5 | format_code_in_doc_comments = true 6 | wrap_comments = true 7 | edition = "2021" 8 | version = "Two" 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at torkleyy@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First of all, thank you for your interest in contributing. 4 | 5 | ## Bug reports / feature requests 6 | 7 | If you experience any bugs or have feature requests, please [file an issue]. 8 | 9 | [file an issue]: https://github.com/amethyst/specs/issues/new/choose 10 | 11 | ## Getting started 12 | 13 | If you want to contribute code, please read the following sections. 14 | 15 | There are couple of recommended step before you start working on a ticket: 16 | 17 | 1. If you haven't already, please read [the Specs book](https://specs.amethyst.rs/docs/tutorials/) 18 | 2. Please make sure you read our [Code of Conduct](CODE_OF_CONDUCT.md) 19 | 3. Refer to the [architecture section](#architecture) below to gain some overview 20 | 4. Please continue with the next section (Creating Pull Requests) 21 | 22 | ## Creating Pull Requests 23 | 24 | Once you worked through the [Getting started](#getting-started) section, congrutalations! You can 25 | now start working on [a ticket from the issue tracker][tick]. If there's no ticket yet, please 26 | create one in advance, except your PR provides 27 | 28 | * more documentation / minor fixes to existing documentation, 29 | * more tests or 30 | * more benchmarks. 31 | 32 | [tick]: https://github.com/amethyst/specs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc 33 | 34 | Please leave a comment on the respective issue that you're working on it, so we don't end up 35 | with two PRs for the same ticket. While working on a branch, you can refer to the [basic guides] 36 | below in case you are not experienced with Git. 37 | 38 | [basic guides]: #git 39 | 40 | Once you've made the changes you want to make, or in case you want early feedback / help, 41 | please create a PR. The PR template provides more detail on the last steps required. 42 | 43 | ## Architecture 44 | 45 | Specs exposes an interface for working with the ECS pattern, and it makes heavy use of other 46 | Rust projects to accomplish that. 47 | 48 | Specs can be divided into two big parts: 49 | 50 | 1. execution of code 51 | 2. managing data 52 | 53 | Number 1 is served by [`shred`](https://github.com/amethyst/shred); it provides the following pieces: 54 | 55 | * `System`; this is the central interface for defining logic 56 | * `Dispatcher` and `DispatcherBuilder` - these are responsible for building a plan for how to run systems 57 | (in parallel & sequentially) 58 | 59 | Additionally, `shred` also provides the central piece for number 2: 60 | 61 | * `World`; everything that a `System` can access is stored inside. 62 | 63 | Specs itself defines component storages (which are also stored inside the `World`). 64 | For those, [`hibitset`](https://github.com/amethyst/hibitset/) is used to: 65 | 66 | * store the indices (= entity ids) with an existing component 67 | * allow efficient joining over sparse component storages 68 | 69 | More details for the individual components can be found in the respective API documentation. 70 | 71 | ## Git 72 | 73 | This project has some basic guidelines for working with git commits: 74 | 75 | * Merge commits are only created by bors; PRs should rebase onto master 76 | (see the [rebasing section](#dealing-with-upstream-changes) below) 77 | 78 | ### Cloning the repository 79 | 80 | The following sections assume you have cloned the repository as follows: 81 | 82 | ```sh 83 | git clone https://github.com/amethyst/specs 84 | ``` 85 | 86 | (if you're using SSH, you need to use `git@github.com:amethyst/specs`) 87 | 88 | Git by default sets the remote branch you cloned from to `origin`. That's what 89 | is usually used for the fork, so let's change that: 90 | 91 | ```sh 92 | git remote rename origin upstream 93 | git remote add origin https://github.com/my_user_name/specs 94 | ``` 95 | 96 | (if you're using SSH, you need to use `git@github.com:my_user_name/specs`) 97 | 98 | ### Starting a new branch 99 | 100 | ```sh 101 | git fetch upstream && git checkout -b foo upstream/master 102 | ``` 103 | 104 | ### Dealing with upstream changes 105 | 106 | Please use rebase over merge, since the latter is bad for the commit history. 107 | If you're new to git, here's how to do that: 108 | 109 | ```sh 110 | git fetch upstream 111 | ``` 112 | 113 | Assuming `upstream` is the upstream repo, this will fetch the latest changes. 114 | 115 | Use the following with care if you're new to Git; better make a backup! 116 | 117 | ```sh 118 | git rebase upstream/master 119 | ``` 120 | 121 | This will try to re-apply your commits on top of the upstream changes. If there 122 | are conflicts, you'll be asked to fix them; once done, add the changes with 123 | `git add -A` and use `git rebase --continue`. Repeat until there are no more 124 | conflicts. 125 | 126 | That should be it. Note that you'll have to force-push to your branch in case 127 | you have pushed before. 128 | 129 | ### Squashing commits 130 | 131 | If you created more commits than intended, it can be a good idea to combine some 132 | of your commits. Note that this, again, should be used with care if you don't 133 | know what you're doing; better create a backup before! 134 | 135 | ```sh 136 | git rebase --interactive HEAD~$num_commits # replace this 137 | ``` 138 | 139 | You just need to replace `num_commits` with the number of commits you want to 140 | edit (use `git log` if unsure). 141 | 142 | Now you can simply change some commits to `s` or `f` to merge them into the 143 | above commits. Once done, you'll be asked for the new commit messages. 144 | 145 | That should be it. Note that you'll have to force-push to your branch in case 146 | you have pushed before. 147 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "specs" 3 | version = "0.20.0" 4 | description = """ 5 | Specs is an Entity-Component-System library written in Rust. 6 | """ 7 | documentation = "https://docs.rs/specs/" 8 | repository = "https://github.com/amethyst/specs" 9 | homepage = "https://amethyst.github.io/specs" 10 | readme = "README.md" 11 | keywords = ["gamedev", "ecs", "entity", "component"] 12 | categories = ["concurrency", "game-engines"] 13 | license = "MIT OR Apache-2.0" 14 | authors = ["slide-rs hackers"] 15 | include = ["/src", "/examples", "/benches", "/README.md", "/LICENSE-MIT", "/LICENSE-APACHE"] 16 | edition = "2021" 17 | rust-version = "1.70.0" 18 | 19 | # the `storage_cmp` and `storage_sparse` benches are called from `benches_main` 20 | autobenches = false 21 | 22 | [dependencies] 23 | ahash = "0.8.6" 24 | crossbeam-queue = "0.3" 25 | hibitset = { version = "0.6.4", default-features = false } 26 | log = "0.4.8" 27 | shred = { version = "0.16.0", default-features = false } 28 | shrev = "1.1.1" 29 | tuple_utils = "0.4.0" 30 | nougat = "0.2.3" 31 | 32 | rayon = { version = "1.5.1", optional = true } 33 | serde = { version = "1.0.104", optional = true, features = ["serde_derive"] } 34 | specs-derive = { version = "0.4.1", path = "specs-derive", optional = true } 35 | uuid = { version = "1.0", optional = true, features = ["v4", "serde"] } 36 | 37 | [features] 38 | default = ["parallel"] 39 | parallel = ["dep:rayon", "shred/parallel", "hibitset/parallel"] 40 | uuid_entity = ["dep:uuid", "serde"] 41 | stdweb = ["dep:uuid", "uuid?/js"] 42 | storage-event-control = [] 43 | derive = ["shred-derive", "specs-derive"] 44 | nightly = ["shred/nightly"] 45 | 46 | shred-derive = ["shred/shred-derive"] 47 | 48 | [package.metadata.docs.rs] 49 | features = ["parallel", "serde", "shred-derive", "specs-derive", "uuid_entity", "storage-event-control"] 50 | 51 | [dev-dependencies] 52 | nalgebra = "0.32" 53 | criterion = "0.3.1" 54 | ron = "0.8.1" 55 | rand = "0.8" 56 | serde_json = "1.0.48" 57 | shred = { version = "0.16.0", default-features = false, features = ["shred-derive"] } 58 | specs-derive = { path = "specs-derive", version = "0.4.1" } 59 | 60 | [[example]] 61 | name = "async" 62 | [[example]] 63 | name = "basic" 64 | [[example]] 65 | name = "bitset" 66 | [[example]] 67 | name = "cluster_bomb" 68 | [[example]] 69 | name = "full" 70 | [[example]] 71 | name = "lend_join" 72 | test = true 73 | [[example]] 74 | name = "ordered_track" 75 | [[example]] 76 | name = "saveload" 77 | required-features = ["serde"] 78 | [[example]] 79 | name = "slices" 80 | [[example]] 81 | name = "track" 82 | 83 | [[bench]] 84 | name = "benches_main" 85 | harness = false 86 | 87 | [[bench]] 88 | name = "parallel" 89 | 90 | [[bench]] 91 | name = "world" 92 | harness = false 93 | 94 | [[bench]] 95 | name = "big_or_small" 96 | 97 | [workspace] 98 | members = ["specs-derive"] 99 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 The Specs Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Specs 2 | 3 | > **S**pecs **P**arallel **ECS** 4 | 5 | [![Build Status][bi]][bl] [![Crates.io][ci]][cl] [![Gitter][gi]][gl] ![MIT/Apache][li] [![Docs.rs][di]][dl] 6 | 7 | [bi]: https://github.com/amethyst/specs/actions/workflows/ci.yml/badge.svg?branch=master 8 | [bl]: https://github.com/amethyst/specs/actions/workflows/ci.yml 9 | 10 | [ci]: https://img.shields.io/crates/v/specs.svg 11 | [cl]: https://crates.io/crates/specs/ 12 | 13 | [gi]: https://badges.gitter.im/slide-rs/specs.svg 14 | [gl]: https://gitter.im/slide-rs/specs 15 | 16 | [li]: https://img.shields.io/crates/l/specs.svg?maxAge=2592000 17 | 18 | [di]: https://docs.rs/specs/badge.svg 19 | [dl]: https://docs.rs/specs/ 20 | 21 | 22 | Specs is an Entity-Component System written in Rust. 23 | Unlike most other ECS libraries out there, it provides 24 | 25 | * easy parallelism 26 | * high flexibility 27 | * contains 5 different storages for components, which can be extended by the user 28 | * its types are mostly not coupled, so you can easily write some part yourself and 29 | still use Specs 30 | * `System`s may read from and write to components and resources, can depend on each 31 | other and you can use barriers to force several stages in system execution 32 | * high performance for real-world applications 33 | 34 | Minimum Rust version: 1.70 35 | 36 | ## [Link to the book][book] 37 | 38 | [book]: https://amethyst.github.io/specs/docs/tutorials/ 39 | 40 | ## Example 41 | 42 | ```rust 43 | use specs::prelude::*; 44 | 45 | // A component contains data 46 | // which is associated with an entity. 47 | #[derive(Debug)] 48 | struct Vel(f32); 49 | 50 | impl Component for Vel { 51 | type Storage = VecStorage; 52 | } 53 | 54 | #[derive(Debug)] 55 | struct Pos(f32); 56 | 57 | impl Component for Pos { 58 | type Storage = VecStorage; 59 | } 60 | 61 | struct SysA; 62 | 63 | impl<'a> System<'a> for SysA { 64 | // These are the resources required for execution. 65 | // You can also define a struct and `#[derive(SystemData)]`, 66 | // see the `full` example. 67 | type SystemData = (WriteStorage<'a, Pos>, ReadStorage<'a, Vel>); 68 | 69 | fn run(&mut self, (mut pos, vel): Self::SystemData) { 70 | // The `.join()` combines multiple component storages, 71 | // so we get access to all entities which have 72 | // both a position and a velocity. 73 | for (pos, vel) in (&mut pos, &vel).join() { 74 | pos.0 += vel.0; 75 | } 76 | } 77 | } 78 | 79 | fn main() { 80 | // The `World` is our 81 | // container for components 82 | // and other resources. 83 | let mut world = World::new(); 84 | world.register::(); 85 | world.register::(); 86 | 87 | // An entity may or may not contain some component. 88 | 89 | world.create_entity().with(Vel(2.0)).with(Pos(0.0)).build(); 90 | world.create_entity().with(Vel(4.0)).with(Pos(1.6)).build(); 91 | world.create_entity().with(Vel(1.5)).with(Pos(5.4)).build(); 92 | 93 | // This entity does not have `Vel`, so it won't be dispatched. 94 | world.create_entity().with(Pos(2.0)).build(); 95 | 96 | // This builds a dispatcher. 97 | // The third parameter of `with` specifies 98 | // logical dependencies on other systems. 99 | // Since we only have one, we don't depend on anything. 100 | // See the `full` example for dependencies. 101 | let mut dispatcher = DispatcherBuilder::new().with(SysA, "sys_a", &[]).build(); 102 | // This will call the `setup` function of every system. 103 | // In this example this has no effect since we already registered our components. 104 | dispatcher.setup(&mut world); 105 | 106 | // This dispatches all the systems in parallel (but blocking). 107 | dispatcher.dispatch(&mut world); 108 | } 109 | ``` 110 | 111 | Please look into [the examples directory](examples) for more. 112 | 113 | ## Public dependencies 114 | 115 | | crate | version | 116 | |----------|------------------------------------------------------------------------------------------------| 117 | | hibitset | [![hibitset](https://img.shields.io/crates/v/hibitset.svg)](https://crates.io/crates/hibitset) | 118 | | rayon | [![rayon](https://img.shields.io/crates/v/rayon.svg)](https://crates.io/crates/rayon) | 119 | | shred | [![shred](https://img.shields.io/crates/v/shred.svg)](https://crates.io/crates/shred) | 120 | | shrev | [![shrev](https://img.shields.io/crates/v/shrev.svg)](https://crates.io/crates/shrev) | 121 | 122 | ## Contribution 123 | 124 | Contribution is very welcome! If you didn't contribute before, 125 | just filter for issues with "easy" or "good first issue" label. 126 | Please note that your contributions are assumed to be dual-licensed under Apache-2.0/MIT. 127 | -------------------------------------------------------------------------------- /benches/benches_main.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | #[macro_use] 4 | extern crate criterion; 5 | extern crate specs; 6 | extern crate test; 7 | 8 | macro_rules! group { 9 | ($name:ident,$($benches:path),*) => { 10 | pub fn $name(c: &mut Criterion) { 11 | $( 12 | $benches(c); 13 | )* 14 | } 15 | }; 16 | } 17 | 18 | mod storage_cmp; 19 | mod storage_sparse; 20 | 21 | pub use test::black_box; 22 | 23 | use storage_cmp::benches_storages; 24 | use storage_sparse::benches_sparse; 25 | 26 | criterion_main!(benches_storages, benches_sparse); 27 | -------------------------------------------------------------------------------- /benches/big_or_small.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate nalgebra; 4 | extern crate rand; 5 | extern crate shred; 6 | extern crate specs; 7 | extern crate test; 8 | 9 | use nalgebra::Vector3; 10 | use specs::prelude::*; 11 | use test::Bencher; 12 | 13 | type Vec3 = Vector3; 14 | 15 | // -- Components -- 16 | #[derive(Clone, Debug)] 17 | struct Small(Vec3, Vec3); 18 | 19 | impl Component for Small { 20 | type Storage = VecStorage; 21 | } 22 | 23 | #[derive(Clone, Debug)] 24 | struct Small2(Vec3, Vec3); 25 | 26 | impl Component for Small2 { 27 | type Storage = VecStorage; 28 | } 29 | 30 | #[derive(Clone, Debug)] 31 | struct Big(Vec3, Vec3, Vec3, Vec3); 32 | 33 | impl Component for Big { 34 | type Storage = VecStorage; 35 | } 36 | 37 | // -- Systems -- 38 | 39 | struct SmallSystem; 40 | 41 | impl<'a> System<'a> for SmallSystem { 42 | type SystemData = (ReadStorage<'a, Small>, WriteStorage<'a, Small2>); 43 | 44 | fn run(&mut self, (small, mut small2): Self::SystemData) { 45 | for (s, s2) in (&small, &mut small2).join() { 46 | s2.0.y += s.0.x; 47 | } 48 | } 49 | } 50 | 51 | struct BigSystem; 52 | 53 | impl<'a> System<'a> for BigSystem { 54 | type SystemData = (WriteStorage<'a, Big>,); 55 | 56 | fn run(&mut self, (mut big,): Self::SystemData) { 57 | for (b,) in (&mut big,).join() { 58 | b.0.y += b.0.x; 59 | } 60 | } 61 | } 62 | 63 | #[bench] 64 | fn bench_big(b: &mut Bencher) { 65 | let mut world = World::new(); 66 | 67 | world.register::(); 68 | 69 | for _ in 0..100000 { 70 | world 71 | .create_entity() 72 | .with(Big( 73 | Vec3::new(0.0, 0.0, 0.0), 74 | Vec3::new(0.0, 0.0, 0.0), 75 | Vec3::new(0.0, 0.0, 0.0), 76 | Vec3::new(0.0, 0.0, 0.0), 77 | )) 78 | .build(); 79 | } 80 | 81 | let mut dispatch = DispatcherBuilder::new() 82 | .with(BigSystem, "big_sys", &[]) 83 | .build(); 84 | 85 | b.iter(|| { 86 | dispatch.dispatch(&mut world); 87 | world.maintain(); 88 | }) 89 | } 90 | 91 | #[bench] 92 | fn bench_small(b: &mut Bencher) { 93 | let mut world = World::new(); 94 | 95 | world.register::(); 96 | world.register::(); 97 | 98 | for _ in 0..100000 { 99 | world 100 | .create_entity() 101 | .with(Small(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 0.0))) 102 | .with(Small2(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 0.0))) 103 | .build(); 104 | } 105 | 106 | let mut dispatch = DispatcherBuilder::new() 107 | .with(SmallSystem, "small_sys", &[]) 108 | .build(); 109 | 110 | b.iter(|| { 111 | dispatch.dispatch(&mut world); 112 | world.maintain(); 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /benches/storage_sparse.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Bencher, Criterion}; 2 | 3 | use super::black_box; 4 | 5 | macro_rules! setup { 6 | ($num:expr => [ $( $comp:ty ),* ] ) => { 7 | pub fn setup(filter: bool, insert: bool, sparsity: u32) -> (World, Vec) { 8 | let mut w = World::new(); 9 | $( 10 | w.register::<$comp>(); 11 | )* 12 | 13 | let eids: Vec<_> = (0..$num) 14 | .flat_map(|i| { 15 | let mut builder = w.create_entity(); 16 | if insert { 17 | if i % sparsity == 0 { 18 | $( 19 | builder = builder.with::<$comp>(<$comp>::default()); 20 | )* 21 | } 22 | } 23 | 24 | if !filter || i % sparsity == 0 { 25 | Some(builder.build()) 26 | } else { 27 | None 28 | } 29 | }) 30 | .collect(); 31 | 32 | (w, eids) 33 | } 34 | } 35 | } 36 | 37 | macro_rules! gap { 38 | ($storage:ident, $name:ident => $sparsity:expr) => { 39 | mod $name { 40 | use super::{ 41 | super::{black_box, Bencher, Criterion}, 42 | setup, CompBool, CompInt, 43 | }; 44 | use specs::prelude::*; 45 | 46 | fn insert(bencher: &mut Bencher) { 47 | let (world, entities) = setup(true, false, $sparsity); 48 | let mut ints = world.write_storage::(); 49 | let mut bools = world.write_storage::(); 50 | 51 | bencher.iter(move || { 52 | for &entity in &entities { 53 | ints.insert(entity, CompInt::default()).unwrap(); 54 | bools.insert(entity, CompBool::default()).unwrap(); 55 | } 56 | }); 57 | } 58 | 59 | fn remove(bencher: &mut Bencher) { 60 | let (world, entities) = setup(true, true, $sparsity); 61 | let mut ints = world.write_storage::(); 62 | let mut bools = world.write_storage::(); 63 | 64 | bencher.iter(move || { 65 | for &entity in &entities { 66 | ints.remove(entity); 67 | bools.remove(entity); 68 | } 69 | }); 70 | } 71 | 72 | fn get(bencher: &mut Bencher) { 73 | let (world, entities) = setup(false, true, $sparsity); 74 | let ints = world.read_storage::(); 75 | let bools = world.read_storage::(); 76 | 77 | bencher.iter(move || { 78 | for &entity in &entities { 79 | black_box(ints.get(entity)); 80 | black_box(bools.get(entity)); 81 | } 82 | }); 83 | } 84 | 85 | pub fn benches(c: &mut Criterion) { 86 | c.bench_function( 87 | &format!("sparse insert {}/{}", $sparsity, stringify!($storage)), 88 | |b| insert(b), 89 | ) 90 | .bench_function( 91 | &format!("sparse remove {}/{}", $sparsity, stringify!($storage)), 92 | |b| remove(b), 93 | ) 94 | .bench_function( 95 | &format!("sparse get {}/{}", $sparsity, stringify!($storage)), 96 | |b| get(b), 97 | ); 98 | } 99 | } 100 | }; 101 | } 102 | 103 | macro_rules! tests { 104 | ($mod:ident => $storage:ident) => { 105 | mod $mod { 106 | use criterion::Criterion; 107 | use specs::prelude::*; 108 | 109 | pub static NUM: u32 = 100_000; 110 | 111 | pub struct CompInt(u32); 112 | pub struct CompBool(bool); 113 | 114 | impl Default for CompInt { 115 | fn default() -> Self { 116 | Self(0) 117 | } 118 | } 119 | 120 | impl Default for CompBool { 121 | fn default() -> Self { 122 | Self(true) 123 | } 124 | } 125 | 126 | impl Component for CompInt { 127 | type Storage = ::specs::storage::$storage; 128 | } 129 | impl Component for CompBool { 130 | type Storage = ::specs::storage::$storage; 131 | } 132 | 133 | setup!(NUM => [ CompInt, CompBool ]); 134 | 135 | gap!($storage, sparse_1 => 1); 136 | gap!($storage, sparse_2 => 2); 137 | gap!($storage, sparse_4 => 4); 138 | gap!($storage, sparse_8 => 8); 139 | gap!($storage, sparse_128 => 128); 140 | gap!($storage, sparse_256 => 256); 141 | gap!($storage, sparse_512 => 512); 142 | gap!($storage, sparse_1024 => 1024); 143 | gap!($storage, sparse_10000 => 10_000); 144 | gap!($storage, sparse_50000 => 50_000); 145 | 146 | group!( 147 | benches, 148 | sparse_1::benches, 149 | sparse_2::benches, 150 | sparse_4::benches, 151 | sparse_8::benches, 152 | sparse_128::benches, 153 | sparse_256::benches, 154 | sparse_512::benches, 155 | sparse_1024::benches, 156 | sparse_10000::benches, 157 | sparse_50000::benches 158 | ); 159 | } 160 | }; 161 | } 162 | 163 | tests!(vec_storage => VecStorage); 164 | tests!(dense_vec_storage => DenseVecStorage); 165 | tests!(hashmap_storage => HashMapStorage); 166 | tests!(btree_storage => BTreeStorage); 167 | 168 | criterion_group!( 169 | benches_sparse, 170 | vec_storage::benches, 171 | dense_vec_storage::benches, 172 | hashmap_storage::benches, 173 | btree_storage::benches 174 | ); 175 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | disallowed-types = ["std::collections::HashMap"] -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # See docs: https://embarkstudios.github.io/cargo-deny/checks/index.html 2 | 3 | [advisories] 4 | unmaintained = "warn" 5 | vulnerability = "warn" 6 | yanked = "deny" 7 | notice = "deny" 8 | 9 | [bans] 10 | multiple-versions = "warn" 11 | wildcards = "deny" 12 | skip = [ 13 | { name = "itertools", version = "0.9.0" }, # criterion brings in two versions 14 | ] 15 | 16 | [sources] 17 | unknown-registry = "deny" 18 | unknown-git = "deny" 19 | required-git-spec = "rev" 20 | 21 | [licenses] 22 | copyleft = "deny" 23 | 24 | # Run `cargo deny list` to see which crates use which license 25 | # and add them to this array if we accept them 26 | 27 | allow = ["MIT", "Apache-2.0", "Unlicense", "BSD-3-Clause"] 28 | 29 | # We want really high confidence when inferring licenses from text 30 | confidence-threshold = 0.93 31 | 32 | exceptions = [ 33 | { allow = ["Unicode-DFS-2016"], name = "unicode-ident" }, 34 | ] 35 | -------------------------------------------------------------------------------- /docs/tutorials/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "The Specs Book" 3 | multilingual = false 4 | author = "Thomas Schaller" 5 | description = "Introduction to ECS and Specs tutorials." 6 | -------------------------------------------------------------------------------- /docs/tutorials/src/01_intro.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Welcome to The Specs Book, an introduction to [ECS] and the Specs API. 4 | This book is targeted at beginners; guiding you through all the difficulties of 5 | setting up, building, and structuring a game with an ECS. 6 | 7 | [ECS]: https://en.wikipedia.org/wiki/Entity–component–system 8 | 9 | Specs is an ECS library that allows parallel system execution, with both low 10 | overhead and high flexibility, different storage types and a type-level 11 | system data model. It is mainly used for games and simulations, where it allows 12 | to structure code using composition over inheritance. 13 | 14 | Additional documentation is available on `docs.rs`: 15 | 16 | * [API documentation for Specs](https://docs.rs/specs) 17 | 18 | You don't yet know what an ECS is all about? The next section 19 | is for you! In case you already know what an ECS is, just skip it. 20 | 21 | ## What's an ECS? 22 | 23 | The term **ECS** is a shorthand for Entity-component system. These are the three 24 | core concepts. Each **entity** is associated with some **components**. Those entities and 25 | components are processed by **systems**. This way, you have your data (components) 26 | completely separated from the behaviour (systems). An entity just logically 27 | groups components; so a `Velocity` component can be applied to the `Position` component 28 | of the same entity. 29 | 30 | ECS is sometimes seen as a counterpart to Object-Oriented Programming. I wouldn't 31 | say that's one hundred percent true, but let me give you some comparisons. 32 | 33 | In OOP, your player might look like this (I've used Java for the example): 34 | 35 | ```java 36 | public class Player extends Character { 37 | private final Transform transform; 38 | private final Inventory inventory; 39 | } 40 | ``` 41 | 42 | There are several limitations here: 43 | 44 | * There is either no multiple inheritance or it brings other problems with it, 45 | like [the diamond problem][dp]; moreover, you have to think about "*is* the player 46 | a collider or does it *have* a collider?" 47 | * You cannot easily extend the player with modding; all the attributes are hardcoded. 48 | * Imagine you want to add a NPC, which looks like this: 49 | 50 | [dp]: https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem 51 | 52 | ```java 53 | public class Npc extends Character { 54 | private final Transform transform; 55 | private final Inventory inventory; 56 | private final boolean isFriendly; 57 | } 58 | ``` 59 | 60 | Now you have stuff duplicated; you would have to write mostly identical code for 61 | your player and the NPC, even though e.g. they both share a transform. 62 | 63 | Entity-component relationship 64 | 65 | This is where ECS comes into play: Components are *associated* with entities; you can just insert components, whenever you like. 66 | One entity may or may not have a certain component. You can see an `Entity` as an ID into component tables, as illustrated in the 67 | diagram below. We could theoretically store all the components together with the entity, but that would be very inefficient; 68 | you'll see how these tables work in [chapter 5]. 69 | 70 | This is how an `Entity` is implemented; it's just 71 | 72 | ```rust,ignore 73 | struct Entity(u32, Generation); 74 | ``` 75 | 76 | where the first field is the id and the second one is the generation, used to check 77 | if the entity has been deleted. 78 | 79 | Here's another illustration of the relationship between components and entities. `Force`, `Mass` and `Velocity` are all components here. 80 | 81 | [chapter 5]: ./05_storages.html 82 | 83 | Component tables 84 | 85 | `Entity 1` has each of those components, `Entity 2` only a `Force`, etc. 86 | 87 | Now we're only missing the last character in ECS - the "S" for `System`. Whereas components and entities are purely data, 88 | systems contain all the logic of your application. A system typically iterates over all entities that fulfill specific constraints, 89 | like "has both a force and a mass". Based on this data a system will execute code, e.g. produce a velocity out of the force and the mass. 90 | This is the additional advantage I wanted to point out with the `Player` / `Npc` example; in an ECS, you can simply add new attributes 91 | to entities and that's also how you define behaviour in Specs (this is called [data-driven] programming). 92 | 93 | [data-driven]: https://en.wikipedia.org/wiki/Data-driven_programming 94 | 95 | System flow 96 | 97 | By simply adding a force to an entity that has a mass, you can make it move, because a `Velocity` will be produced for it. 98 | 99 | ## Where to use an ECS? 100 | 101 | In case you were looking for a general-purpose library for doing things the data-oriented way, I have to disappoint you; there are none. 102 | ECS libraries are best-suited for creating games or simulations, but they do not magically make your code more data-oriented. 103 | 104 | --- 105 | 106 | Okay, now that you were given a rough overview, let's continue 107 | to [Chapter 2][c2] where we'll build our first actual application with Specs. 108 | 109 | [am]: https://www.amethyst.rs 110 | [ra]: https://github.com/rayon-rs/rayon 111 | [c2]: ./02_hello_world.html 112 | 113 | -------------------------------------------------------------------------------- /docs/tutorials/src/03_dispatcher.md: -------------------------------------------------------------------------------- 1 | # Dispatcher 2 | 3 | ## When to use a `Dispatcher` 4 | 5 | The `Dispatcher` allows you to automatically parallelize 6 | system execution where possible, using the [fork-join model][fj] to split up the 7 | work and merge the result at the end. It requires a bit more planning 8 | and may have a little bit more overhead, but it's pretty convenient, 9 | especially when you're building a big game where you don't 10 | want to do this manually. 11 | 12 | [fj]: https://en.wikipedia.org/wiki/Fork–join_model 13 | 14 | ## Building a dispatcher 15 | 16 | First of all, we have to build such a dispatcher. 17 | 18 | ```rust,ignore 19 | use specs::DispatcherBuilder; 20 | 21 | let mut dispatcher = DispatcherBuilder::new() 22 | .with(HelloWorld, "hello_world", &[]) 23 | .build(); 24 | ``` 25 | 26 | Let's see what this does. After creating the builder, 27 | we add a new 28 | 29 | 1) system object (`HelloWorld`) 30 | 2) with some name (`"hello_world""`) 31 | 3) and no dependencies (`&[]`). 32 | 33 | The name can be used to specify that system 34 | as a dependency of another one. But we don't have a second 35 | system yet. 36 | 37 | ## Creating another system 38 | 39 | ```rust,ignore 40 | struct UpdatePos; 41 | 42 | impl<'a> System<'a> for UpdatePos { 43 | type SystemData = (ReadStorage<'a, Velocity>, 44 | WriteStorage<'a, Position>); 45 | } 46 | ``` 47 | 48 | Let's talk about the system data first. What you see here is a **tuple**, which we are using as our `SystemData`. 49 | In fact, `SystemData` is implemented for all tuples with up to 26 other types implementing `SystemData` in it. 50 | 51 | > Notice that `ReadStorage` and `WriteStorage` *are* implementors of `SystemData` 52 | themselves, that's why we could use the first one for our `HelloWorld` system 53 | without wrapping it in a tuple; for more information see 54 | [the Chapter about system data][cs]. 55 | 56 | [cs]: ./06_system_data.html 57 | 58 | To complete the implementation block, here's the `run` method: 59 | 60 | ```rust,ignore 61 | fn run(&mut self, (vel, mut pos): Self::SystemData) { 62 | use specs::Join; 63 | for (vel, pos) in (&vel, &mut pos).join() { 64 | pos.x += vel.x * 0.05; 65 | pos.y += vel.y * 0.05; 66 | } 67 | } 68 | ``` 69 | 70 | Now the `.join()` method also makes sense: it joins the two component 71 | storages, so that you either get no new element or a new element with 72 | both components, meaning that entities with only a `Position`, only 73 | a `Velocity` or none of them will be skipped. The `0.05` fakes the 74 | so called **delta time** which is the time needed for one frame. 75 | We have to hardcode it right now, because it's not a component (it's the 76 | same for every entity). The solution to this are `Resource`s, see 77 | [the next Chapter][c4]. 78 | 79 | [c4]: ./04_resources.html 80 | 81 | ## Adding a system with a dependency 82 | 83 | Okay, we'll add two more systems *after* the `HelloWorld` system: 84 | 85 | ```rust,ignore 86 | .with(UpdatePos, "update_pos", &["hello_world"]) 87 | .with(HelloWorld, "hello_updated", &["update_pos"]) 88 | ``` 89 | The `UpdatePos` system now depends on the `HelloWorld` system and will only 90 | be executed after the dependency has finished. The final `HelloWorld` system prints the resulting updated positions. 91 | 92 | Now to execute all the systems, just do 93 | 94 | ```rust,ignore 95 | dispatcher.dispatch(&mut world); 96 | ``` 97 | 98 | ## Full example code 99 | 100 | Here the code for this chapter: 101 | 102 | ```rust,ignore 103 | use specs::{Builder, Component, DispatcherBuilder, ReadStorage, 104 | System, VecStorage, World, WorldExt, WriteStorage}; 105 | 106 | #[derive(Debug)] 107 | struct Position { 108 | x: f32, 109 | y: f32, 110 | } 111 | 112 | impl Component for Position { 113 | type Storage = VecStorage; 114 | } 115 | 116 | #[derive(Debug)] 117 | struct Velocity { 118 | x: f32, 119 | y: f32, 120 | } 121 | 122 | impl Component for Velocity { 123 | type Storage = VecStorage; 124 | } 125 | 126 | struct HelloWorld; 127 | 128 | impl<'a> System<'a> for HelloWorld { 129 | type SystemData = ReadStorage<'a, Position>; 130 | 131 | fn run(&mut self, position: Self::SystemData) { 132 | use specs::Join; 133 | 134 | for position in position.join() { 135 | println!("Hello, {:?}", &position); 136 | } 137 | } 138 | } 139 | 140 | struct UpdatePos; 141 | 142 | impl<'a> System<'a> for UpdatePos { 143 | type SystemData = (ReadStorage<'a, Velocity>, 144 | WriteStorage<'a, Position>); 145 | 146 | fn run(&mut self, (vel, mut pos): Self::SystemData) { 147 | use specs::Join; 148 | for (vel, pos) in (&vel, &mut pos).join() { 149 | pos.x += vel.x * 0.05; 150 | pos.y += vel.y * 0.05; 151 | } 152 | } 153 | } 154 | 155 | fn main() { 156 | let mut world = World::new(); 157 | world.register::(); 158 | world.register::(); 159 | 160 | // Only the second entity will get a position update, 161 | // because the first one does not have a velocity. 162 | world.create_entity().with(Position { x: 4.0, y: 7.0 }).build(); 163 | world 164 | .create_entity() 165 | .with(Position { x: 2.0, y: 5.0 }) 166 | .with(Velocity { x: 0.1, y: 0.2 }) 167 | .build(); 168 | 169 | let mut dispatcher = DispatcherBuilder::new() 170 | .with(HelloWorld, "hello_world", &[]) 171 | .with(UpdatePos, "update_pos", &["hello_world"]) 172 | .with(HelloWorld, "hello_updated", &["update_pos"]) 173 | .build(); 174 | 175 | dispatcher.dispatch(&mut world); 176 | world.maintain(); 177 | } 178 | ``` 179 | 180 | --- 181 | 182 | [The next chapter][c4] will be a really short chapter about `Resource`s, 183 | a way to share data between systems which only exist independent of 184 | entities (as opposed to 0..1 times per entity). 185 | -------------------------------------------------------------------------------- /docs/tutorials/src/04_resources.md: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | This (short) chapter will explain the concept of resources, data 4 | which is shared between systems. 5 | 6 | First of all, when would you need resources? There's actually a great 7 | example in [chapter 3][c3], where we just faked the delta time when applying 8 | the velocity. Let's see how we can do this the right way. 9 | 10 | [c3]: ./03_dispatcher.html 11 | 12 | ```rust,ignore 13 | #[derive(Default)] 14 | struct DeltaTime(f32); 15 | ``` 16 | 17 | > **Note:** In practice you may want to use `std::time::Duration` instead, 18 | because you shouldn't use `f32`s for durations in an actual game, because 19 | they're not precise enough. 20 | 21 | Adding this resource to our world is pretty easy: 22 | 23 | ```rust,ignore 24 | world.insert(DeltaTime(0.05)); // Let's use some start value 25 | ``` 26 | 27 | To update the delta time, just use 28 | 29 | ```rust,ignore 30 | use specs::WorldExt; 31 | 32 | let mut delta = world.write_resource::(); 33 | *delta = DeltaTime(0.04); 34 | ``` 35 | 36 | ## Accessing resources from a system 37 | 38 | As you might have guessed, there's a type implementing system data 39 | specifically for resources. It's called `Read` (or `Write` for 40 | write access). 41 | 42 | So we can now rewrite our system: 43 | 44 | ```rust,ignore 45 | use specs::{Read, ReadStorage, System, WriteStorage}; 46 | 47 | struct UpdatePos; 48 | 49 | impl<'a> System<'a> for UpdatePos { 50 | type SystemData = (Read<'a, DeltaTime>, 51 | ReadStorage<'a, Velocity>, 52 | WriteStorage<'a, Position>); 53 | 54 | fn run(&mut self, data: Self::SystemData) { 55 | let (delta, vel, mut pos) = data; 56 | 57 | // `Read` implements `Deref`, so it 58 | // coerces to `&DeltaTime`. 59 | let delta = delta.0; 60 | 61 | for (vel, pos) in (&vel, &mut pos).join() { 62 | pos.x += vel.x * delta; 63 | pos.y += vel.y * delta; 64 | } 65 | } 66 | } 67 | ``` 68 | 69 | Note that all resources that a system accesses must be registered with 70 | `world.insert(resource)` before that system is run, or you will get a 71 | panic. If the resource has a `Default` implementation, this step is usually 72 | done during `setup`, but again we will come back to this in a later chapter. 73 | 74 | For more information on `SystemData`, see [the system data chapter][cs]. 75 | 76 | ## `Default` for resources 77 | 78 | As we have learned in previous chapters, to fetch a `Resource` in our 79 | `SystemData`, we use `Read` or `Write`. However, there is one issue we 80 | have not mentioned yet, and that is the fact that `Read` and `Write` require 81 | `Default` to be implemented on the resource. This is because Specs will 82 | automatically try to add a `Default` version of a resource to the `World` 83 | during `setup` (we will come back to the `setup` stage in the next chapter). 84 | But how do we handle the case when we can't implement `Default` for our resource? 85 | 86 | There are actually three ways of doing this: 87 | 88 | * Using a custom `SetupHandler` implementation, you can provide this in `SystemData` 89 | with `Read<'a, Resource, TheSetupHandlerType>`. 90 | * By replacing `Read` and `Write` with `ReadExpect` and `WriteExpect`, which will 91 | cause the first dispatch of the `System` to panic unless the resource has been 92 | added manually to `World` first. 93 | * By using `Option>`, if the resource really is optional. Note 94 | that the order here is important, using `Read<'a, Option>` will not 95 | result in the same behavior (it will try to fetch `Option` from `World`, 96 | instead of doing an optional check if `Resource` exists). 97 | 98 | 99 | [cs]: ./06_system_data.html 100 | 101 | --- 102 | 103 | In [the next chapter][c5], you will learn about the different storages 104 | and when to use which one. 105 | 106 | [c5]: 05_storages.html 107 | -------------------------------------------------------------------------------- /docs/tutorials/src/05_storages.md: -------------------------------------------------------------------------------- 1 | # Storages 2 | 3 | Specs contains a bunch of different storages, all built and optimized for 4 | different use cases. But let's see some basics first. 5 | 6 | ## Storage basics 7 | 8 | What you specify in a component `impl`-block is an `UnprotectedStorage`. 9 | Each `UnprotectedStorage` exposes an unsafe getter which does not 10 | perform any checks whether the requested index for the component is valid 11 | (the id of an entity is the index of its component). To allow checking them 12 | and speeding up iteration, we have something called hierarchical bitsets, 13 | provided by [`hibitset`](https://github.com/slide-rs/hibitset). 14 | 15 | > **Note:** In case you don't know anything about bitsets, 16 | you can safely skip the following section about it. Just keep 17 | in mind that we have some mask which tracks for 18 | which entities a component exists. 19 | 20 | How does it speed up the iteration? A hierarchical bitset is essentially 21 | a multi-layer bitset, where each upper layer "summarizes" multiple bits 22 | of the underlying layers. That means as soon as one of the underlying 23 | bits is `1`, the upper one also becomes `1`, so that we can skip a whole 24 | range of indices if an upper bit is `0` in that section. In case it's `1`, 25 | we go down by one layer and perform the same steps again (it currently 26 | has 4 layers). 27 | 28 | ## Storage overview 29 | 30 | Here a list of the storages with a short description and a link 31 | to the corresponding heading. 32 | 33 | |Storage Type |Description |Optimized for | 34 | |:----------------------:|----------------------------------------------------|------------------------------| 35 | | [`BTreeStorage`] | Works with a `BTreeMap` | no particular case | 36 | | [`DenseVecStorage`] | Uses a redirection table | fairly often used components | 37 | | [`HashMapStorage`] | Uses a `HashMap` | rare components | 38 | | [`NullStorage`] | Can flag entities | doesn't depend on rarity | 39 | | [`VecStorage`] | Uses a sparse `Vec`, empty slots are uninitialized | commonly used components | 40 | | [`DefaultVecStorage`] | Uses a sparse `Vec`, empty slots contain `Default` | commonly used components | 41 | 42 | [`BTreeStorage`]: #btreestorage 43 | [`DenseVecStorage`]: #densevecstorage 44 | [`HashMapStorage`]: #hashmapstorage 45 | [`NullStorage`]: #nullstorage 46 | [`VecStorage`]: #vecstorage 47 | [`DefaultVecStorage`]: #defaultvecstorage 48 | 49 | ## Slices 50 | 51 | Certain storages provide access to component slices: 52 | 53 | |Storage Type | Slice type | Density | Indices | 54 | |:----------------------:|---------------------|---------|---------------| 55 | | [`DenseVecStorage`] | `&[T]` | Dense | Arbitrary | 56 | | [`VecStorage`] | `&[MaybeUninit]` | Sparse | Entity `id()` | 57 | | [`DefaultVecStorage`] | `&[T]` | Sparse | Entity `id()` | 58 | 59 | This is intended as an advanced technique. Component slices provide 60 | maximally efficient reads and writes, but they are incompatible with 61 | many of the usual abstractions which makes them more difficult to use. 62 | 63 | ## `BTreeStorage` 64 | 65 | It works using a `BTreeMap` and it's meant to be the default storage 66 | in case you're not sure which one to pick, because it fits all scenarios 67 | fairly well. 68 | 69 | ## `DenseVecStorage` 70 | 71 | This storage uses two `Vec`s, one containing the actual data and the other 72 | one which provides a mapping from the entity id to the index for the data vec 73 | (it's a redirection table). This is useful when your component is bigger 74 | than a `usize` because it consumes less RAM. 75 | 76 | `DenseVecStorage` provides `as_slice()` and `as_mut_slice()` accessors 77 | which return `&[T]`. The indices in this slice do not correspond to entity 78 | IDs, nor do they correspond to indices in any other storage, nor do they 79 | correspond to indices in this storage at a different point in time. 80 | 81 | ## `HashMapStorage` 82 | 83 | This should be used for components which are associated with very few entities, 84 | because it provides a lower insertion cost and is packed together more tightly. 85 | You should not use it for frequently used components, because the hashing cost would definitely 86 | be noticeable. 87 | 88 | ## `NullStorage` 89 | 90 | As already described in the overview, the `NullStorage` does itself 91 | only contain a user-defined ZST (=Zero Sized Type; a struct with no data in it, 92 | like `struct Synced;`). 93 | Because it's wrapped in a so-called `MaskedStorage`, insertions and deletions 94 | modify the mask, so it can be used for flagging entities (like in this example 95 | for marking an entity as `Synced`, which could be used to only synchronize 96 | some of the entities over the network). 97 | 98 | ## `VecStorage` 99 | 100 | This one has only one vector (as opposed to the `DenseVecStorage`). It 101 | just leaves uninitialized gaps where we don't have any component. 102 | Therefore it would be a waste of memory to use this storage for 103 | rare components, but it's best suited for commonly used components 104 | (like transform values). 105 | 106 | `VecStorage` provides `as_slice()` and `as_mut_slice()` accessors which 107 | return `&[MaybeUninit]`. Consult the `Storage::mask()` to determine 108 | which indices are populated. Slice indices cannot be converted to `Entity` 109 | values because they lack a generation counter, but they do correspond to 110 | `Entity::id()`s, so indices can be used to collate between multiple 111 | `VecStorage`s. 112 | 113 | ## `DefaultVecStorage` 114 | 115 | This storage works exactly like `VecStorage`, but instead of leaving gaps 116 | uninitialized, it fills them with the component's default value. This 117 | requires the component to `impl Default`, and it results in more memory 118 | writes than `VecStorage`. 119 | 120 | `DefaultVecStorage` provides `as_slice()` and `as_mut_slice()` accessors 121 | which return `&[T]`. `Storage::mask()` can be used to determine which 122 | indices are in active use, but all indices are fully initialized, so the 123 | `mask()` is not necessary for safety. `DefaultVecStorage` indices all 124 | correspond with each other, with `VecStorage` indices, and with 125 | `Entity::id()`s. 126 | -------------------------------------------------------------------------------- /docs/tutorials/src/06_system_data.md: -------------------------------------------------------------------------------- 1 | # System Data 2 | 3 | Every system can request data which it needs to run. This data can be specified 4 | using the `System::SystemData` type. Typical implementors of the `SystemData` trait 5 | are `ReadStorage`, `WriteStorage`, `Read`, `Write`, `ReadExpect`, `WriteExpect` and `Entities`. 6 | A tuple of types implementing `SystemData` automatically also implements `SystemData`. 7 | This means you can specify your `System::SystemData` as follows: 8 | 9 | ```rust 10 | struct Sys; 11 | 12 | impl<'a> System<'a> for Sys { 13 | type SystemData = (WriteStorage<'a, Pos>, ReadStorage<'a, Vel>); 14 | 15 | fn run(&mut self, (pos, vel): Self::SystemData) { 16 | /* ... */ 17 | } 18 | } 19 | ``` 20 | 21 | It is very important that you don't request both a `ReadStorage` and a `WriteStorage` 22 | for the same component or a `Read` and a `Write` for the same resource. 23 | This is just like the borrowing rules of Rust, where you can't borrow something 24 | mutably and immutably at the same time. In Specs, we have to check this at 25 | runtime, thus you'll get a panic if you don't follow this rule. 26 | 27 | ## Accessing Entities 28 | 29 | You want to create/delete entities from a system? There is 30 | good news for you. You can use `Entities` to do that. 31 | It implements `SystemData` so just put it in your `SystemData` tuple. 32 | 33 | > Don't confuse `specs::Entities` with `specs::EntitiesRes`. 34 | While the latter one is the actual resource, the former one is a type 35 | definition for `Read`. 36 | 37 | Please note that you may never write to these `Entities`, so only 38 | use `Read`. Even though it's immutable, you can atomically create 39 | and delete entities with it. Just use the `.create()` and `.delete()` 40 | methods, respectively. 41 | 42 | For example, if you wanted to delete an entity based after a period of time you could write something similar like this. 43 | 44 | ```rust 45 | pub struct Life { 46 | life: f32, 47 | } 48 | 49 | struct DecaySys; 50 | 51 | impl<'a> System<'a> for DecaySys { 52 | type SystemData = (Entities<'a>, WriteStorage<'a, Life>); 53 | 54 | fn run(&mut self, (entities, mut life): Self::SystemData) { 55 | for (e, life) in (&entities, &mut life).join() { 56 | if life < 0.0 { 57 | entities.delete(e); 58 | } else { 59 | life -= 1.0; 60 | } 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | > Just remember after dynamic entity deletion, a call to `World::maintain` is necessary in order to make the changes 67 | persistent and delete associated components. 68 | 69 | ## Adding and removing components 70 | 71 | Adding or removing components can be done by modifying 72 | either the component storage directly with a `WriteStorage` 73 | or lazily using the `LazyUpdate` resource. 74 | 75 | ```rust,ignore 76 | use specs::{Component, Read, LazyUpdate, NullStorage, System, Entities, WriteStorage}; 77 | 78 | struct Stone; 79 | impl Component for Stone { 80 | type Storage = NullStorage; 81 | } 82 | 83 | struct StoneCreator; 84 | impl<'a> System<'a> for StoneCreator { 85 | type SystemData = ( 86 | Entities<'a>, 87 | WriteStorage<'a, Stone>, 88 | Read<'a, LazyUpdate>, 89 | ); 90 | 91 | fn run(&mut self, (entities, mut stones, updater): Self::SystemData) { 92 | let stone = entities.create(); 93 | 94 | // 1) Either we insert the component by writing to its storage 95 | stones.insert(stone, Stone); 96 | 97 | // 2) or we can lazily insert it with `LazyUpdate` 98 | updater.insert(stone, Stone); 99 | } 100 | } 101 | ``` 102 | 103 | > **Note:** After using `LazyUpdate` a call to `World::maintain` 104 | is necessary to actually execute the changes. 105 | 106 | ## `SetupHandler` / `Default` for resources 107 | 108 | Please refer to [the resources chapter for automatic creation of resources][c4]. 109 | 110 | [c4]: ./04_resources.html 111 | 112 | ## Specifying `SystemData` 113 | 114 | As mentioned earlier, `SystemData` is implemented for tuples up to 26 elements. Should you ever need 115 | more, you could even nest these tuples. However, at some point it becomes hard to keep track of all the elements. 116 | That's why you can also create your own `SystemData` bundle using a struct: 117 | 118 | ```rust,ignore 119 | extern crate specs; 120 | 121 | use specs::prelude::*; 122 | // `shred` needs to be in scope for the `SystemData` derive. 123 | use specs::shred; 124 | 125 | #[derive(SystemData)] 126 | pub struct MySystemData<'a> { 127 | positions: ReadStorage<'a, Position>, 128 | velocities: ReadStorage<'a, Velocity>, 129 | forces: ReadStorage<'a, Force>, 130 | 131 | delta: Read<'a, DeltaTime>, 132 | game_state: Write<'a, GameState>, 133 | } 134 | ``` 135 | 136 | Make sure to enable the `shred-derive` feature in your `Cargo.toml`: 137 | 138 | ```toml 139 | specs = { version = "*", features = ["shred-derive"] } 140 | ``` 141 | -------------------------------------------------------------------------------- /docs/tutorials/src/07_setup.md: -------------------------------------------------------------------------------- 1 | # The `setup` stage 2 | 3 | So far for all our component storages and resources, we've been adding 4 | them to the `World` manually. In Specs, this is not required if you use 5 | `setup`. This is a manually invoked stage that goes through `SystemData` 6 | and calls `register`, `insert`, etc. for all (with some exceptions) 7 | components and resources found. The `setup` function can be found in 8 | the following locations: 9 | 10 | * `ReadStorage`, `WriteStorage`, `Read`, `Write` 11 | * `SystemData` 12 | * `System` 13 | * `RunNow` 14 | * `Dispatcher` 15 | * `ParSeq` 16 | 17 | During setup, all components encountered will be registered, and all 18 | resources that have a `Default` implementation or a custom `SetupHandler` 19 | will be added. Note that resources encountered in `ReadExpect` and `WriteExpect` 20 | will not be added to the `World` automatically. 21 | 22 | The recommended way to use `setup` is to run it on `Dispatcher` or `ParSeq` 23 | after the system graph is built, but before the first `dispatch`. This will go 24 | through all `System`s in the graph, and call `setup` on each. 25 | 26 | Let's say you began by registering Components and Resources first: 27 | 28 | ```rust,ignore 29 | use specs::prelude::*; 30 | 31 | #[derive(Default)] 32 | struct Gravity; 33 | 34 | struct Velocity; 35 | 36 | impl Component for Velocity { 37 | type Storage = VecStorage; 38 | } 39 | 40 | struct SimulationSystem; 41 | 42 | impl<'a> System<'a> for SimulationSystem { 43 | type SystemData = (Read<'a, Gravity>, WriteStorage<'a, Velocity>); 44 | 45 | fn run(&mut self, _: Self::SystemData) {} 46 | } 47 | 48 | fn main() { 49 | let mut world = World::new(); 50 | world.insert(Gravity); 51 | world.register::(); 52 | 53 | for _ in 0..5 { 54 | world.create_entity().with(Velocity).build(); 55 | } 56 | 57 | let mut dispatcher = DispatcherBuilder::new() 58 | .with(SimulationSystem, "simulation", &[]) 59 | .build(); 60 | 61 | dispatcher.dispatch(&mut world); 62 | world.maintain(); 63 | } 64 | 65 | ``` 66 | 67 | You could get rid of that phase by calling `setup()` and re-ordering your main function: 68 | 69 | ```rust,ignore 70 | fn main() { 71 | let mut world = World::new(); 72 | let mut dispatcher = DispatcherBuilder::new() 73 | .with(SimulationSystem, "simulation", &[]) 74 | .build(); 75 | 76 | dispatcher.setup(&mut world); 77 | 78 | for _ in 0..5 { 79 | world.create_entity().with(Velocity).build(); 80 | } 81 | 82 | dispatcher.dispatch(&mut world); 83 | world.maintain(); 84 | } 85 | 86 | ``` 87 | 88 | 89 | ## Custom `setup` functionality 90 | 91 | The good qualities of `setup` don't end here however. We can also use `setup` 92 | to create our non-`Default` resources, and also to initialize our `System`s! 93 | We do this by custom implementing the `setup` function in our `System`. 94 | 95 | Let's say we have a `System` that process events, using `shrev::EventChannel`: 96 | 97 | ```rust,ignore 98 | struct Sys { 99 | reader: ReaderId, 100 | } 101 | 102 | impl<'a> System<'a> for Sys { 103 | type SystemData = Read<'a, EventChannel>; 104 | 105 | fn run(&mut self, events: Self::SystemData) { 106 | for event in events.read(&mut self.reader) { 107 | [..] 108 | } 109 | } 110 | } 111 | ``` 112 | 113 | This looks pretty OK, but there is a problem here if we want to use `setup`. 114 | The issue is that `Sys` needs a `ReaderId` on creation, but to get a `ReaderId`, 115 | we need `EventChannel` to be initialized. This means the user of `Sys` need 116 | to create the `EventChannel` themselves and add it manually to the `World`. 117 | We can do better! 118 | 119 | ```rust,ignore 120 | use specs::prelude::*; 121 | 122 | #[derive(Default)] 123 | struct Sys { 124 | reader: Option>, 125 | } 126 | 127 | impl<'a> System<'a> for Sys { 128 | type SystemData = Read<'a, EventChannel>; 129 | 130 | fn run(&mut self, events: Self::SystemData) { 131 | for event in events.read(&mut self.reader.as_mut().unwrap()) { 132 | [..] 133 | } 134 | } 135 | 136 | fn setup(&mut self, world: &mut World) { 137 | Self::SystemData::setup(world); 138 | self.reader = Some(world.fetch_mut::>().register_reader()); 139 | } 140 | } 141 | ``` 142 | 143 | This is much better; we can now use `setup` to fully initialize `Sys` without 144 | requiring our users to create and add resources manually to `World`! 145 | 146 | **If we override the `setup` function on a `System`, it is vitally important that we remember to add `Self::SystemData::setup(world);`, or setup will not be performed for the `System`s `SystemData`.** 147 | This could cause panics during setup or during the first dispatch. 148 | 149 | ## Setting up in bulk 150 | 151 | In the case of libraries making use of `specs`, it is sometimes helpful to provide 152 | a way to add many things at once. 153 | It's generally recommended to provide a standalone function to register multiple 154 | Components/Resources at once, while allowing the user to add individual systems 155 | by themselves. 156 | 157 | ```rust,ignore 158 | fn add_physics_engine(world: &mut World, config: LibraryConfig) -> Result<(), LibraryError> { 159 | world.register::(); 160 | // etc 161 | } 162 | ``` 163 | -------------------------------------------------------------------------------- /docs/tutorials/src/08_join.md: -------------------------------------------------------------------------------- 1 | # Joining components 2 | 3 | In the last chapter, we learned how to access resources using `SystemData`. 4 | To access our components with it, we can just request a `ReadStorage` and use 5 | `Storage::get` to retrieve the component associated to an entity. This works quite 6 | well if you want to access a single component, but what if you want to 7 | iterate over many components? Maybe some of them are required, others might 8 | be optional and maybe there is even a need to exclude some components? 9 | If we wanted to do that using only `Storage::get`, the code would become very ugly. 10 | So instead we worked out a way to conveniently specify that. This concept is 11 | known as "joining". 12 | 13 | ## Basic joining 14 | 15 | We've already seen some basic examples of joining in the last chapters, for 16 | example we saw how to join over two storages: 17 | 18 | ```rust,ignore 19 | for (pos, vel) in (&mut pos_storage, &vel_storage).join() { 20 | *pos += *vel; 21 | } 22 | ``` 23 | 24 | This simply iterates over the position and velocity components of 25 | all entities that have both these components. That means all the 26 | specified components are **required**. 27 | 28 | Sometimes, we want not only get the components of entities, 29 | but also the entity value themselves. To do that, we can simply join over 30 | `&EntitiesRes`. 31 | 32 | ```rust,ignore 33 | for (ent, pos, vel) in (&*entities, &mut pos_storage, &vel_storage).join() { 34 | println!("Processing entity: {:?}", ent); 35 | *pos += *vel; 36 | } 37 | ``` 38 | 39 | The returned entity value can also be used to get a component from a storage as usual. 40 | 41 | ## Optional components 42 | 43 | The previous example will iterate over all entities that have all the components 44 | we need, but what if we want to iterate over an entity whether it has a component 45 | or not? 46 | 47 | To do that, we can wrap the `Storage` with `maybe()`: it wraps the `Storage` in a 48 | `MaybeJoin` struct which, rather than returning a component directly, returns 49 | `None` if the component is missing and `Some(T)` if it's there. 50 | 51 | ```rust,ignore 52 | for (pos, vel, mass) in 53 | (&mut pos_storage, &vel_storage, (&mut mass_storage).maybe()).join() { 54 | println!("Processing entity: {:?}", ent); 55 | *pos += *vel; 56 | 57 | if let Some(mass) = mass { 58 | let x = *vel / 300_000_000.0; 59 | let y = 1 - x * x; 60 | let y = y.sqrt(); 61 | mass.current = mass.constant / y; 62 | } 63 | } 64 | ``` 65 | 66 | In this example we iterate over all entities with a position and a velocity and 67 | perform the calculation for the new position as usual. However, in case the entity 68 | has a mass, we also calculate the current mass based on the velocity. 69 | Thus, mass is an **optional** component here. 70 | 71 | **WARNING:** Do not have a join of only `MaybeJoin`s. Otherwise the join will iterate 72 | over every single index of the bitset. If you want a join with all `MaybeJoin`s, 73 | add an EntitiesRes to the join as well to bound the join to all entities that are alive. 74 | 75 | ### Manually fetching components with `Storage::get()` 76 | 77 | Even though `join()`ing over `maybe()` should be preferred because it can optimize how entities are 78 | iterated, it's always possible to fetch a component manually using `Storage::get()` 79 | or `Storage::get_mut()`. 80 | For example, say that you want to damage a target entity every tick, but only if 81 | it has an `Health`: 82 | 83 | ```rust,ignore 84 | for (target, damage) in (&target_storage, &damage_storage).join() { 85 | 86 | let target_health: Option<&mut Health> = health_storage.get_mut(target.ent); 87 | if let Some(target_health) = target_health { 88 | target_health.current -= damage.value; 89 | } 90 | } 91 | ``` 92 | 93 | Even though this is a somewhat contrived example, this is a common pattern 94 | when entities interact. 95 | 96 | ## Excluding components 97 | 98 | If you want to filter your selection by excluding all entities 99 | with a certain component type, you can use the not operator (`!`) 100 | on the respective component storage. Its return value is a unit (`()`). 101 | 102 | ```rust,ignore 103 | for (ent, pos, vel, ()) in ( 104 | &*entities, 105 | &mut pos_storage, 106 | &vel_storage, 107 | !&frozen_storage, 108 | ).join() { 109 | println!("Processing entity: {:?}", ent); 110 | *pos += *vel; 111 | } 112 | ``` 113 | 114 | This will simply iterate over all entities that 115 | 116 | * have a position 117 | * have a velocity 118 | * do not have a `Frozen` component 119 | 120 | ## How joining works 121 | 122 | You can call `join()` on everything that implements the `Join` trait. 123 | The method call always returns an iterator. `Join` is implemented for 124 | 125 | * `&ReadStorage` / `&WriteStorage` (gives back a reference to the components) 126 | * `&mut WriteStorage` (gives back a mutable reference to the components) 127 | * `&EntitiesRes` (returns `Entity` values) 128 | * bitsets 129 | 130 | We think the last point here is pretty interesting, because 131 | it allows for even more flexibility, as you will see in the next 132 | section. 133 | 134 | ## Joining over bitsets 135 | 136 | Specs is using `hibitset`, a library which provides layered bitsets 137 | (those were part of Specs once, but it was decided that a separate 138 | library could be useful for others). 139 | 140 | These bitsets are used with the component storages to determine 141 | which entities the storage provides a component value for. Also, 142 | `Entities` is using bitsets, too. You can even create your 143 | own bitsets and add or remove entity ids: 144 | 145 | ```rust,ignore 146 | use hibitset::{BitSet, BitSetLike}; 147 | 148 | let mut bitset = BitSet::new(); 149 | bitset.add(entity1.id()); 150 | bitset.add(entity2.id()); 151 | ``` 152 | 153 | `BitSet`s can be combined using the standard binary operators, 154 | `&`, `|` and `^`. Additionally, you can negate them using `!`. 155 | This allows you to combine and filter components in multiple ways. 156 | 157 | --- 158 | 159 | This chapter has been all about looping over components; but we can do more 160 | than sequential iteration! Let's look at some parallel code in the next 161 | chapter. 162 | -------------------------------------------------------------------------------- /docs/tutorials/src/09_parallel_join.md: -------------------------------------------------------------------------------- 1 | # Parallel Join 2 | 3 | As mentioned in the chapter dedicated to how to [dispatch][c3] systems, 4 | Specs automatically parallelizes system execution when there are non-conflicting 5 | system data requirements (Two `System`s conflict if their `SystemData` needs access 6 | to the same resource where at least one of them needs write access to it). 7 | 8 | [c3]: ./03_dispatcher.html 9 | 10 | ## Basic parallelization 11 | 12 | What isn't automatically parallelized by Specs are 13 | the joins made within a single system: 14 | 15 | ```rust,ignore 16 | fn run(&mut self, (vel, mut pos): Self::SystemData) { 17 | use specs::Join; 18 | // This loop runs sequentially on a single thread. 19 | for (vel, pos) in (&vel, &mut pos).join() { 20 | pos.x += vel.x * 0.05; 21 | pos.y += vel.y * 0.05; 22 | } 23 | } 24 | ``` 25 | 26 | This means that, if there are hundreds of thousands of entities and only a few 27 | systems that actually can be executed in parallel, then the full power 28 | of CPU cores cannot be fully utilized. 29 | 30 | To fix this potential inefficiency and to parallelize the joining, the `join` 31 | method call can be exchanged for `par_join`: 32 | 33 | ```rust,ignore 34 | fn run(&mut self, (vel, mut pos): Self::SystemData) { 35 | use rayon::prelude::*; 36 | use specs::ParJoin; 37 | 38 | // Parallel joining behaves similarly to normal joining 39 | // with the difference that iteration can potentially be 40 | // executed in parallel by a thread pool. 41 | (&vel, &mut pos) 42 | .par_join() 43 | .for_each(|(vel, pos)| { 44 | pos.x += vel.x * 0.05; 45 | pos.y += vel.y * 0.05; 46 | }); 47 | } 48 | ``` 49 | 50 | > There is always overhead in parallelization, so you should carefully profile to see if there are benefits in the 51 | switch. If you have only a few things to iterate over then sequential join is faster. 52 | 53 | The `par_join` method produces a type implementing rayon's [`ParallelIterator`][ra] 54 | trait which provides lots of helper methods to manipulate the iteration, 55 | the same way the normal `Iterator` trait does. 56 | 57 | [ra]: https://docs.rs/rayon/1.0.0/rayon/iter/trait.ParallelIterator.html 58 | -------------------------------------------------------------------------------- /docs/tutorials/src/10_rendering.md: -------------------------------------------------------------------------------- 1 | # Rendering 2 | 3 | Rendering is often a little bit tricky when you're dealing with a multi-threaded ECS. 4 | That's why we have something called "thread-local systems". 5 | 6 | There are two things to keep in mind about thread-local systems: 7 | 8 | 1) They're always executed at the end of dispatch. 9 | 2) They cannot have dependencies; you just add them in the order you want them to run. 10 | 11 | Adding one is a simple line added to the builder code: 12 | 13 | ```rust,ignore 14 | DispatcherBuilder::new() 15 | .with_thread_local(RenderSys); 16 | ``` 17 | 18 | ## Amethyst 19 | 20 | As for Amethyst, it's very easy because Specs is already integrated. So there's no special effort 21 | required, just look at the current examples. 22 | 23 | ## Piston 24 | 25 | Piston has an event loop which looks like this: 26 | 27 | ```rust,ignore 28 | while let Some(event) = window.poll_event() { 29 | // Handle event 30 | } 31 | ``` 32 | 33 | Now, we'd like to do as much as possible in the ECS, so we feed in input as a 34 | [resource](./04_resources.html). 35 | This is what your code could look like: 36 | 37 | ```rust,ignore 38 | struct ResizeEvents(Vec<(u32, u32)>); 39 | 40 | world.insert(ResizeEvents(Vec::new())); 41 | 42 | while let Some(event) = window.poll_event() { 43 | match event { 44 | Input::Resize(x, y) => world.write_resource::().0.push((x, y)), 45 | // ... 46 | } 47 | } 48 | ``` 49 | 50 | The actual dispatching should happen every time the `Input::Update` event occurs. 51 | 52 | --- 53 | 54 | > If you want a section for your game engine added, feel free to submit a PR! 55 | -------------------------------------------------------------------------------- /docs/tutorials/src/11_advanced_component.md: -------------------------------------------------------------------------------- 1 | # Advanced strategies for components 2 | 3 | So now that we have a fairly good grasp on the basics of Specs, 4 | it's time that we start experimenting with more advanced patterns! 5 | 6 | ## Marker components 7 | 8 | Say we want to add a drag force to only some entities that have velocity, but 9 | let other entities move about freely without drag. 10 | 11 | The most common way is to use a marker component for this. A marker component 12 | is a component without any data that can be added to entities to "mark" them 13 | for processing, and can then be used to narrow down result sets using `Join`. 14 | 15 | Some code for the drag example to clarify: 16 | 17 | ```rust,ignore 18 | #[derive(Component)] 19 | #[storage(NullStorage)] 20 | pub struct Drag; 21 | 22 | #[derive(Component)] 23 | pub struct Position { 24 | pub pos: [f32; 3], 25 | } 26 | 27 | #[derive(Component)] 28 | pub struct Velocity { 29 | pub velocity: [f32; 3], 30 | } 31 | 32 | struct Sys { 33 | drag: f32, 34 | } 35 | 36 | impl<'a> System<'a> for Sys { 37 | type SystemData = ( 38 | ReadStorage<'a, Drag>, 39 | ReadStorage<'a, Velocity>, 40 | WriteStorage<'a, Position>, 41 | ); 42 | 43 | fn run(&mut self, (drag, velocity, mut position): Self::SystemData) { 44 | // Update positions with drag 45 | for (pos, vel, _) in (&mut position, &velocity, &drag).join() { 46 | pos += vel - self.drag * vel * vel; 47 | } 48 | // Update positions without drag 49 | for (pos, vel, _) in (&mut position, &velocity, !&drag).join() { 50 | pos += vel; 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | Using `NullStorage` is recommended for marker components, since they don't contain 57 | any data and as such will not consume any memory. This means we can represent them using 58 | only a bitset. Note that `NullStorage` will only work for components that are ZST (i.e. a 59 | struct without fields). 60 | 61 | ## Modeling entity relationships and hierarchy 62 | 63 | A common use case where we need a relationship between entities is having a third person 64 | camera following the player around. We can model this using a targeting component 65 | referencing the player entity. 66 | 67 | A simple implementation might look something like this: 68 | 69 | ```rust,ignore 70 | 71 | #[derive(Component)] 72 | pub struct Target { 73 | target: Entity, 74 | offset: Vector3, 75 | } 76 | 77 | pub struct FollowTargetSys; 78 | 79 | impl<'a> System<'a> for FollowTargetSys { 80 | type SystemData = ( 81 | Entities<'a>, 82 | ReadStorage<'a, Target>, 83 | WriteStorage<'a, Transform>, 84 | ); 85 | 86 | fn run(&mut self, (entity, target, transform): Self::SystemData) { 87 | for (entity, t) in (&*entity, &target).join() { 88 | let new_transform = transform.get(t.target).cloned().unwrap() + t.offset; 89 | *transform.get_mut(entity).unwrap() = new_transform; 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | We could also model this as a resource (more about that in the next section), but it could 96 | be useful to be able to have multiple entities following targets, so modeling this with 97 | a component makes sense. This could in extension be used to model large scale hierarchical 98 | structure (scene graphs). For a generic implementation of such a hierarchical system, check 99 | out the crate [`specs-hierarchy`][sh]. 100 | 101 | [sh]: https://github.com/rustgd/specs-hierarchy 102 | 103 | ## Entity targeting 104 | 105 | Imagine we're building a team based FPS game, and we want to add a spectator mode, where the 106 | spectator can pick a player to follow. In this scenario each player will have a camera defined 107 | that is following them around, and what we want to do is to pick the camera that 108 | we should use to render the scene on the spectator screen. 109 | 110 | The easiest way to deal with this problem is to have a resource with a target entity, that 111 | we can use to fetch the actual camera entity. 112 | 113 | ```rust,ignore 114 | pub struct ActiveCamera(Entity); 115 | 116 | pub struct Render; 117 | 118 | impl<'a> System<'a> for Render { 119 | type SystemData = ( 120 | Read<'a, ActiveCamera>, 121 | ReadStorage<'a, Camera>, 122 | ReadStorage<'a, Transform>, 123 | ReadStorage<'a, Mesh>, 124 | ); 125 | 126 | fn run(&mut self, (active_cam, camera, transform, mesh) : Self::SystemData) { 127 | let camera = camera.get(active_cam.0).unwrap(); 128 | let view_matrix = transform.get(active_cam.0).unwrap().invert(); 129 | // Set projection and view matrix uniforms 130 | for (mesh, transform) in (&mesh, &transform).join() { 131 | // Set world transform matrix 132 | // Render mesh 133 | } 134 | } 135 | } 136 | ``` 137 | 138 | By doing this, whenever the spectator chooses a new player to follow, we simply change 139 | what `Entity` is referenced in the `ActiveCamera` resource, and the scene will be 140 | rendered from that viewpoint instead. 141 | 142 | ## Sorting entities based on component value 143 | 144 | In a lot of scenarios we encounter a need to sort entities based on either a component's 145 | value, or a combination of component values. There are a couple of ways to deal with this 146 | problem. The first and most straightforward is to just sort `Join` results. 147 | 148 | ```rust,ignore 149 | let data = (&entities, &comps).join().collect::>(); 150 | data.sort_by(|&a, &b| ...); 151 | for entity in data.iter().map(|d| d.0) { 152 | // Here we get entities in sorted order 153 | } 154 | ``` 155 | 156 | There are a couple of limitations with this approach, the first being that we will always 157 | process all matched entities every frame (if this is called in a `System` somewhere). This 158 | can be fixed by using `FlaggedStorage` to maintain a sorted `Entity` list in the `System`. 159 | We will talk more about `FlaggedStorage` in the next [chapter][fs]. 160 | 161 | The second limitation is that we do a `Vec` allocation every time, however this can be 162 | alleviated by having a `Vec` in the `System` struct that we reuse every frame. Since we 163 | are likely to keep a fairly steady amount of entities in most situations this could work well. 164 | 165 | [fs]: ./12_tracked.html 166 | -------------------------------------------------------------------------------- /docs/tutorials/src/12_tracked.md: -------------------------------------------------------------------------------- 1 | # `FlaggedStorage` and modification events 2 | 3 | In most games you will have many entities, but from frame to frame there will 4 | usually be components that will only need to be updated when something related is 5 | modified. 6 | 7 | To avoid a lot of unnecessary computation when updating components it 8 | would be nice if we could somehow check for only those entities that are updated 9 | and recalculate only those. 10 | 11 | We might also need to keep an external resource in sync with changes to 12 | components in Specs `World`, and we only want to propagate actual changes, not 13 | do a full sync every frame. 14 | 15 | This is where `FlaggedStorage` comes into play. By wrapping a component's actual 16 | storage in a `FlaggedStorage`, we can subscribe to modification events, and 17 | easily populate bitsets with only the entities that have actually changed. 18 | 19 | Let's look at some code: 20 | 21 | ```rust,ignore 22 | pub struct Data { 23 | [..] 24 | } 25 | 26 | impl Component for Data { 27 | type Storage = FlaggedStorage>; 28 | } 29 | 30 | #[derive(Default)] 31 | pub struct Sys { 32 | pub dirty: BitSet, 33 | pub reader_id: Option>, 34 | } 35 | 36 | impl<'a> System<'a> for Sys { 37 | type SystemData = ( 38 | ReadStorage<'a, Data>, 39 | WriteStorage<'a, SomeOtherData>, 40 | ); 41 | 42 | fn run(&mut self, (data, mut some_other_data): Self::SystemData) { 43 | self.dirty.clear(); 44 | 45 | let events = data.channel().read(self.reader_id.as_mut().unwrap()); 46 | 47 | // Note that we could use separate bitsets here, we only use one to 48 | // simplify the example 49 | for event in events { 50 | match event { 51 | ComponentEvent::Modified(id) | ComponentEvent::Inserted(id) => { 52 | self.dirty.add(*id); 53 | } 54 | // We don't need to take this event into account since 55 | // removed components will be filtered out by the join; 56 | // if you want to, you can use `self.dirty.remove(*id);` 57 | // so the bit set only contains IDs that still exist 58 | ComponentEvent::Removed(_) => (), 59 | } 60 | } 61 | 62 | for (d, other, _) in (&data, &mut some_other_data, &self.dirty).join() { 63 | // Mutate `other` based on the update data in `d` 64 | } 65 | } 66 | 67 | fn setup(&mut self, res: &mut Resources) { 68 | Self::SystemData::setup(res); 69 | self.reader_id = Some( 70 | WriteStorage::::fetch(&res).register_reader() 71 | ); 72 | } 73 | } 74 | ``` 75 | 76 | There are three different event types that we can receive: 77 | 78 | - `ComponentEvent::Inserted` - will be sent when a component is added to the 79 | storage 80 | - `ComponentEvent::Modified` - will be sent when a component is fetched mutably 81 | from the storage 82 | - `ComponentEvent::Removed` - will be sent when a component is removed from the 83 | storage 84 | 85 | ## Gotcha: Iterating `FlaggedStorage` Mutably 86 | 87 | Because of how `ComponentEvent` works, if you iterate mutably over a 88 | component storage using `Join`, all entities that are fetched by the `Join` will 89 | be flagged as modified even if nothing was updated in them. 90 | 91 | For example, this will cause all `comps` components to be flagged as modified: 92 | 93 | ```rust,ignore 94 | // **Never do this** if `comps` uses `FlaggedStorage`. 95 | // 96 | // This will flag all components as modified regardless of whether the inner 97 | // loop actually modified the component. 98 | for comp in (&mut comps).join() { 99 | // ... 100 | } 101 | ``` 102 | 103 | Instead, you will want to either: 104 | 105 | - Restrict the components mutably iterated over, for example by joining with a 106 | `BitSet` or another component storage. 107 | - Iterating over the components use a `RestrictedStorage` and only fetch the 108 | component as mutable if/when needed. 109 | 110 | ## `RestrictedStorage` 111 | 112 | If you need to iterate over a `FlaggedStorage` mutably and don't want every 113 | component to be marked as modified, you can use a `RestrictedStorage` and only 114 | fetch the component as mutable if/when needed. 115 | 116 | ```rust,ignore 117 | for (entity, mut comp) in (&entities, &mut comps.restrict_mut()).join() { 118 | // Check whether this component should be modified, without fetching it as 119 | // mutable. 120 | if comp.get().condition < 5 { 121 | let mut comp = comp.get_mut(); 122 | // ... 123 | } 124 | } 125 | ``` 126 | 127 | ## Start and Stop event emission 128 | 129 | Sometimes you may want to perform some operations on the storage, but you don't 130 | want that these operations produce any event. 131 | 132 | You can use the function `storage.set_event_emission(false)` to suppress the 133 | event writing for of any action. When you want to re activate them you can 134 | simply call `storage.set_event_emission(true)`. 135 | 136 | --- 137 | 138 | _See 139 | [FlaggedStorage Doc](https://docs.rs/specs/latest/specs/struct.FlaggedStorage.html) 140 | for more into._ 141 | -------------------------------------------------------------------------------- /docs/tutorials/src/14_troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | ## `Tried to fetch a resource, but the resource does not exist.` 4 | 5 | This is the most common issue you will face as a new user of Specs. 6 | This panic will occur whenever a `System` is first dispatched, and one or 7 | more of the components and/or resources it uses is missing from `World`. 8 | 9 | There are a few main reasons for this occurring: 10 | 11 | * Forgetting to call `setup` after building a `Dispatcher` or `ParSeq`. Make 12 | sure this is always run before the first dispatch. 13 | * Not adding mandatory resources to `World`. You can usually find these by 14 | searching for occurrences of `ReadExpect` and `WriteExpect`. 15 | * Manually requesting components/resources from `World` (not inside a `System`), 16 | where the component/resource is not used by any `System`s, which is most common 17 | when using the `EntityBuilder`. This is an artifact of how `setup` works, it 18 | will only add what is found inside the used `System`s. 19 | If you use other components/resources, you need to manually register/add these 20 | to `World`. 21 | -------------------------------------------------------------------------------- /docs/tutorials/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # The Specs Book 2 | 3 | - [Introduction](01_intro.md) 4 | - [Hello World](02_hello_world.md) 5 | - [Dispatcher](03_dispatcher.md) 6 | - [Resources](04_resources.md) 7 | - [Storages](05_storages.md) 8 | - [System Data](06_system_data.md) 9 | - [Setup](07_setup.md) 10 | - [Joining components](08_join.md) 11 | - [Parallel Join](09_parallel_join.md) 12 | - [Rendering](10_rendering.md) 13 | - [Advanced component strategies](11_advanced_component.md) 14 | - [FlaggedStorage and modification events](12_tracked.md) 15 | - [Saveload](13_saveload.md) 16 | - [Troubleshooting](14_troubleshooting.md) 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/tutorials/src/images/entity-component.svg: -------------------------------------------------------------------------------- 1 |
Entity
[Not supported by viewer]
Component
[Not supported by viewer]
0..n
0..n
1
1
2 | -------------------------------------------------------------------------------- /docs/tutorials/src/images/system.svg: -------------------------------------------------------------------------------- 1 |
Integration System
Integration System
Force<None>DataDataMassData<None>DataVelocity<None><None>Data
2 | -------------------------------------------------------------------------------- /docs/website/config.toml: -------------------------------------------------------------------------------- 1 | # Should be supplied with --base-url 2 | base_url = "/" 3 | 4 | title = "Specs" 5 | description = "Specs Parallel ECS, a Rust library for parallel data processing using the Entity Component System pattern" 6 | theme = "hyde" 7 | 8 | # Whether to automatically compile all Sass files in the sass directory 9 | compile_sass = true 10 | 11 | [markdown] 12 | # Whether to do syntax highlighting 13 | # Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola 14 | highlight_code = true 15 | 16 | highlight_theme = "inspired-github" 17 | 18 | # Whether to build a search index to be used later on by a JavaScript library 19 | build_search_index = true 20 | 21 | taxonomies = [] 22 | 23 | [extra] 24 | hyde_links = [ 25 | {url = ".", name = "Home"}, 26 | {url = "docs", name = "Docs"}, 27 | {absolut_url = "https://github.com/amethyst/specs", name = "GitHub"}, 28 | ] 29 | -------------------------------------------------------------------------------- /docs/website/content/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Specs" 3 | template = "section-nodate.html" 4 | +++ 5 | 6 | > **S**pecs **P**arallel **ECS** 7 | 8 | Specs is an Entity-Component System written in Rust. 9 | Unlike most other ECS libraries out there, it provides 10 | 11 | * easy parallelism 12 | * high flexibility 13 | * contains 5 different storages for components, which can be extended by the user 14 | * its types are mostly not coupled, so you can easily write some part yourself and 15 | still use Specs 16 | * `System`s may read from and write to components and resources, can depend on each 17 | other and you can use barriers to force several stages in system execution 18 | * high performance for real-world applications 19 | 20 | ## [Link to the book][book] 21 | 22 | [book]: docs/tutorials/ 23 | 24 | ## Example 25 | 26 | ```rust 27 | use specs::prelude::*; 28 | 29 | // A component contains data 30 | // which is associated with an entity. 31 | #[derive(Debug)] 32 | struct Vel(f32); 33 | 34 | impl Component for Vel { 35 | type Storage = VecStorage; 36 | } 37 | 38 | #[derive(Debug)] 39 | struct Pos(f32); 40 | 41 | impl Component for Pos { 42 | type Storage = VecStorage; 43 | } 44 | 45 | struct SysA; 46 | 47 | impl<'a> System<'a> for SysA { 48 | // These are the resources required for execution. 49 | // You can also define a struct and `#[derive(SystemData)]`, 50 | // see the `full` example. 51 | type SystemData = (WriteStorage<'a, Pos>, ReadStorage<'a, Vel>); 52 | 53 | fn run(&mut self, (mut pos, vel): Self::SystemData) { 54 | // The `.join()` combines multiple component storages, 55 | // so we get access to all entities which have 56 | // both a position and a velocity. 57 | for (pos, vel) in (&mut pos, &vel).join() { 58 | pos.0 += vel.0; 59 | } 60 | } 61 | } 62 | 63 | fn main() { 64 | // The `World` is our 65 | // container for components 66 | // and other resources. 67 | let mut world = World::new(); 68 | world.register::(); 69 | world.register::(); 70 | 71 | // An entity may or may not contain some component. 72 | 73 | world.create_entity().with(Vel(2.0)).with(Pos(0.0)).build(); 74 | world.create_entity().with(Vel(4.0)).with(Pos(1.6)).build(); 75 | world.create_entity().with(Vel(1.5)).with(Pos(5.4)).build(); 76 | 77 | // This entity does not have `Vel`, so it won't be dispatched. 78 | world.create_entity().with(Pos(2.0)).build(); 79 | 80 | // This builds a dispatcher. 81 | // The third parameter of `with` specifies 82 | // logical dependencies on other systems. 83 | // Since we only have one, we don't depend on anything. 84 | // See the `full` example for dependencies. 85 | let mut dispatcher = DispatcherBuilder::new() 86 | .with(SysA, "sys_a", &[]).build(); 87 | // This will call the `setup` function of every system. 88 | // In this example this has no effect 89 | // since we already registered our components. 90 | dispatcher.setup(&mut world); 91 | 92 | // This dispatches all the systems in parallel (but blocking). 93 | dispatcher.dispatch(&mut world); 94 | } 95 | ``` 96 | 97 | ## Contribution 98 | 99 | Contribution is very welcome! If you didn't contribute before, 100 | just filter for issues with "easy" or "good first issue" label. 101 | Please note that your contributions are assumed to be dual-licensed under Apache-2.0/MIT. -------------------------------------------------------------------------------- /docs/website/content/pages/docs.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Documentation" 3 | path = "docs" 4 | template = "page-nodate.html" 5 | +++ 6 | 7 | ## API documentation 8 | 9 | * [API docs for latest crates.io release](https://docs.rs/specs) 10 | * [API docs for master](api/specs/) 11 | 12 | ## Tutorials 13 | 14 | * [Tutorials for master](tutorials/) 15 | 16 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | public 3 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Vincent Prouillet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/README.md: -------------------------------------------------------------------------------- 1 | # hyde 2 | Hyde is a brazen two-column [Zola](https://github.com/getzola/zola) based on the Jekyll theme of the same name that pairs a prominent sidebar with uncomplicated content. 3 | 4 | ![Hyde screenshot](https://f.cloud.github.com/assets/98681/1831228/42af6c6a-7384-11e3-98fb-e0b923ee0468.png) 5 | 6 | 7 | ## Contents 8 | 9 | - [Installation](#installation) 10 | - [Options](#options) 11 | - [Sidebar menu](#sidebar-menu) 12 | - [Sticky sidebar content](#sticky-sidebar-content) 13 | - [Themes](#themes) 14 | - [Reverse layout](#reverse-layout) 15 | 16 | ## Installation 17 | First download this theme to your `themes` directory: 18 | 19 | ```bash 20 | $ cd themes 21 | $ git clone https://github.com/getzola/hyde.git 22 | ``` 23 | and then enable it in your `config.toml`: 24 | 25 | ```toml 26 | theme = "hyde" 27 | ``` 28 | 29 | ## Options 30 | 31 | ### Sidebar menu 32 | Set a field in `extra` with a key of `hyde_links`: 33 | ```toml 34 | [extra] 35 | hyde_links = [ 36 | {url = "https://google.com", name = "Google.com"}, 37 | {url = "https://google.fr", name = "Google.fr"}, 38 | ] 39 | ``` 40 | Each link needs to have a `url` and a `name`. 41 | 42 | ### Sticky sidebar content 43 | By default Hyde ships with a sidebar that affixes it's content to the bottom of the sidebar. You can optionally disable this by setting `hyde_sticky` to false in your `config.toml`. 44 | 45 | ### Themes 46 | Hyde ships with eight optional themes based on the [base16 color scheme](https://github.com/chriskempson/base16). Apply a theme to change the color scheme (mostly applies to sidebar and links). 47 | 48 | ![Hyde in red](https://f.cloud.github.com/assets/98681/1831229/42b0b354-7384-11e3-8462-31b8df193fe5.png) 49 | 50 | There are eight themes available at this time. 51 | 52 | ![Hyde theme classes](https://f.cloud.github.com/assets/98681/1817044/e5b0ec06-6f68-11e3-83d7-acd1942797a1.png) 53 | 54 | To use a theme, set the `hyde_theme` field in `config.toml` to any of the themes name: 55 | 56 | ```toml 57 | [extra] 58 | hyde_theme = "theme-base-08" 59 | ``` 60 | 61 | To create your own theme, look to the Themes section of [included CSS file](https://github.com/poole/hyde/blob/master/public/css/hyde.css). Copy any existing theme (they're only a few lines of CSS), rename it, and change the provided colors. 62 | 63 | ### Reverse layout 64 | 65 | ![Hyde with reverse layout](https://f.cloud.github.com/assets/98681/1831230/42b0d3ac-7384-11e3-8d54-2065afd03f9e.png) 66 | 67 | Hyde's page orientation can be reversed by setting `hyde_reverse` to `true` in the `config.toml`. 68 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/config.toml: -------------------------------------------------------------------------------- 1 | base_url = "https://zola-hyde.netlify.com" 2 | compile_sass = true 3 | title = "Hyde theme" 4 | description = "A clean blogging theme" 5 | 6 | [extra] 7 | hyde_sticky = true 8 | hyde_reverse = false 9 | hyde_theme = "" 10 | hyde_links = [ 11 | {url = "https://google.com", name = "About"}, 12 | ] 13 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/sass/hyde.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * __ __ 3 | * /\ \ /\ \ 4 | * \ \ \___ __ __ \_\ \ __ 5 | * \ \ _ `\/\ \/\ \ /'_` \ /'__`\ 6 | * \ \ \ \ \ \ \_\ \/\ \_\ \/\ __/ 7 | * \ \_\ \_\/`____ \ \___,_\ \____\ 8 | * \/_/\/_/`/___/> \/__,_ /\/____/ 9 | * /\___/ 10 | * \/__/ 11 | * 12 | * Designed, built, and released under MIT license by @mdo. Learn more at 13 | * https://github.com/poole/hyde. 14 | */ 15 | 16 | 17 | /* 18 | * Contents 19 | * 20 | * Global resets 21 | * Sidebar 22 | * Container 23 | * Reverse layout 24 | * Themes 25 | */ 26 | 27 | 28 | /* 29 | * Global resets 30 | * 31 | * Update the foundational and global aspects of the page. 32 | */ 33 | 34 | html { 35 | font-family: "PT Sans", Helvetica, Arial, sans-serif; 36 | } 37 | @media (min-width: 48em) { 38 | html { 39 | font-size: 16px; 40 | } 41 | } 42 | @media (min-width: 58em) { 43 | html { 44 | font-size: 20px; 45 | } 46 | } 47 | 48 | 49 | /* 50 | * Sidebar 51 | * 52 | * Flexible banner for housing site name, intro, and "footer" content. Starts 53 | * out above content in mobile and later moves to the side with wider viewports. 54 | */ 55 | 56 | .sidebar { 57 | text-align: center; 58 | padding: 2rem 1rem; 59 | color: rgba(255,255,255,.5); 60 | background-color: #202020; 61 | } 62 | @media (min-width: 48em) { 63 | .sidebar { 64 | position: fixed; 65 | top: 0; 66 | left: 0; 67 | bottom: 0; 68 | width: 18rem; 69 | text-align: left; 70 | } 71 | } 72 | 73 | /* Sidebar links */ 74 | .sidebar a { 75 | color: #fff; 76 | } 77 | 78 | /* About section */ 79 | .sidebar-about h1 { 80 | color: #fff; 81 | margin-top: 0; 82 | font-family: "Abril Fatface", serif; 83 | font-size: 3.25rem; 84 | } 85 | 86 | /* Sidebar nav */ 87 | .sidebar-nav { 88 | padding-left: 0; 89 | list-style: none; 90 | } 91 | .sidebar-nav-item { 92 | display: block; 93 | } 94 | a.sidebar-nav-item:hover, 95 | a.sidebar-nav-item:focus { 96 | text-decoration: underline; 97 | } 98 | .sidebar-nav-item.active { 99 | font-weight: bold; 100 | } 101 | 102 | /* Sticky sidebar 103 | * 104 | * Add the `sidebar-sticky` class to the sidebar's container to affix it the 105 | * contents to the bottom of the sidebar in tablets and up. 106 | */ 107 | 108 | @media (min-width: 48em) { 109 | .sidebar-sticky { 110 | position: absolute; 111 | right: 1rem; 112 | bottom: 1rem; 113 | left: 1rem; 114 | } 115 | } 116 | 117 | 118 | /* Container 119 | * 120 | * Align the contents of the site above the proper threshold with some margin-fu 121 | * with a 25%-wide `.sidebar`. 122 | */ 123 | 124 | .content { 125 | padding-top: 4rem; 126 | padding-bottom: 4rem; 127 | } 128 | 129 | @media (min-width: 48em) { 130 | .content { 131 | max-width: 38rem; 132 | margin-left: 20rem; 133 | margin-right: 2rem; 134 | } 135 | } 136 | 137 | @media (min-width: 64em) { 138 | .content { 139 | margin-left: 22rem; 140 | margin-right: 4rem; 141 | } 142 | } 143 | 144 | 145 | /* 146 | * Reverse layout 147 | * 148 | * Flip the orientation of the page by placing the `.sidebar` on the right. 149 | */ 150 | 151 | @media (min-width: 48em) { 152 | .layout-reverse .sidebar { 153 | left: auto; 154 | right: 0; 155 | } 156 | .layout-reverse .content { 157 | margin-left: 2rem; 158 | margin-right: 20rem; 159 | } 160 | } 161 | 162 | @media (min-width: 64em) { 163 | .layout-reverse .content { 164 | margin-left: 4rem; 165 | margin-right: 22rem; 166 | } 167 | } 168 | 169 | 170 | 171 | /* 172 | * Themes 173 | * 174 | * As of v1.1, Hyde includes optional themes to color the sidebar and links 175 | * within blog posts. To use, add the class of your choosing to the `body`. 176 | */ 177 | 178 | /* Base16 (http://chriskempson.github.io/base16/#default) */ 179 | 180 | /* Red */ 181 | .theme-base-08 .sidebar { 182 | background-color: #ac4142; 183 | } 184 | .theme-base-08 .content a, 185 | .theme-base-08 .related-posts li a:hover { 186 | color: #ac4142; 187 | } 188 | 189 | /* Orange */ 190 | .theme-base-09 .sidebar { 191 | background-color: #d28445; 192 | } 193 | .theme-base-09 .content a, 194 | .theme-base-09 .related-posts li a:hover { 195 | color: #d28445; 196 | } 197 | 198 | /* Yellow */ 199 | .theme-base-0a .sidebar { 200 | background-color: #f4bf75; 201 | } 202 | .theme-base-0a .content a, 203 | .theme-base-0a .related-posts li a:hover { 204 | color: #f4bf75; 205 | } 206 | 207 | /* Green */ 208 | .theme-base-0b .sidebar { 209 | background-color: #90a959; 210 | } 211 | .theme-base-0b .content a, 212 | .theme-base-0b .related-posts li a:hover { 213 | color: #90a959; 214 | } 215 | 216 | /* Cyan */ 217 | .theme-base-0c .sidebar { 218 | background-color: #75b5aa; 219 | } 220 | .theme-base-0c .content a, 221 | .theme-base-0c .related-posts li a:hover { 222 | color: #75b5aa; 223 | } 224 | 225 | /* Blue */ 226 | .theme-base-0d .sidebar { 227 | background-color: #6a9fb5; 228 | } 229 | .theme-base-0d .content a, 230 | .theme-base-0d .related-posts li a:hover { 231 | color: #6a9fb5; 232 | } 233 | 234 | /* Magenta */ 235 | .theme-base-0e .sidebar { 236 | background-color: #aa759f; 237 | } 238 | .theme-base-0e .content a, 239 | .theme-base-0e .related-posts li a:hover { 240 | color: #aa759f; 241 | } 242 | 243 | /* Brown */ 244 | .theme-base-0f .sidebar { 245 | background-color: #8f5536; 246 | } 247 | .theme-base-0f .content a, 248 | .theme-base-0f .related-posts li a:hover { 249 | color: #8f5536; 250 | } 251 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/sass/print.scss: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | display: none !important; 3 | } 4 | 5 | .content { 6 | margin: 0 auto; 7 | width: 100%; 8 | float: none; 9 | display: initial; 10 | } 11 | 12 | .container { 13 | width: 100%; 14 | float: none; 15 | display: initial; 16 | padding-left: 1rem; 17 | padding-right: 1rem; 18 | margin: 0 auto; 19 | } 20 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amethyst/specs/d303a8efca9077debc4f0f9c25bce62c855bbe7d/docs/website/themes/hyde/static/.gitkeep -------------------------------------------------------------------------------- /docs/website/themes/hyde/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | 3 | {% block content %} 4 |
5 |

404: Page not found

6 | Sorry, we've misplaced that URL or it's pointing to something that doesn't exist. Head back home to try finding it again.

7 |
8 | {% endblock content %} 9 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% block title %}{{ config.title }}{% endblock title %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% if config.generate_rss %} 19 | 20 | {% endif %} 21 | 22 | {% block extra_head %} 23 | {% endblock extra_head %} 24 | 25 | 26 | 27 | {% block sidebar %} 28 | 52 | {% endblock sidebar %} 53 | 54 |
55 | {% block content %} 56 |
57 | {% for page in section.pages | reverse %} 58 |
59 |

60 | 61 | {{ page.title }} 62 | 63 |

64 | 65 | 66 |
67 | {% endfor %} 68 |
69 | {% endblock content %} 70 |
71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/templates/page-nodate.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | 3 | {% block content %} 4 |
5 |

{{ page.title }}

6 | {{ page.content | safe }} 7 |
8 | {% endblock content %} 9 | 10 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/templates/section-nodate.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | 3 | {% block content %} 4 |
5 |

{{ section.title }}

6 | {{ section.content | safe }} 7 |
8 | {% endblock content %} 9 | 10 | -------------------------------------------------------------------------------- /docs/website/themes/hyde/theme.toml: -------------------------------------------------------------------------------- 1 | name = "hyde" 2 | description = "A classic blog theme" 3 | license = "MIT" 4 | homepage = "https://github.com/getzola/hyde" 5 | min_version = "0.5.0" 6 | demo = "https://zola-hyde.netlify.com" 7 | 8 | [extra] 9 | hyde_sticky = true 10 | hyde_reverse = false 11 | hyde_theme = "" 12 | hyde_links = [ 13 | ] 14 | 15 | [author] 16 | name = "Vincent Prouillet" 17 | homepage = "https://www.vincentprouillet.com" 18 | 19 | [original] 20 | author = "mdo" 21 | homepage = "http://markdotto.com/" 22 | repo = "https://www.github.com/mdo/hyde" 23 | -------------------------------------------------------------------------------- /examples/async.rs: -------------------------------------------------------------------------------- 1 | extern crate specs; 2 | 3 | use specs::prelude::*; 4 | 5 | // A component contains data which is associated with an entity. 6 | 7 | #[derive(Debug)] 8 | struct Vel(f32); 9 | 10 | impl Component for Vel { 11 | type Storage = VecStorage; 12 | } 13 | 14 | #[derive(Debug)] 15 | struct Pos(f32); 16 | 17 | impl Component for Pos { 18 | type Storage = VecStorage; 19 | } 20 | 21 | struct SysA; 22 | 23 | impl<'a> System<'a> for SysA { 24 | // These are the resources required for execution. 25 | // You can also define a struct and `#[derive(SystemData)]`, 26 | // see the `full` example. 27 | type SystemData = (WriteStorage<'a, Pos>, ReadStorage<'a, Vel>); 28 | 29 | fn run(&mut self, (mut pos, vel): Self::SystemData) { 30 | // The `.join()` combines multiple components, 31 | // so we only access those entities which have 32 | // both of them. 33 | // You could also use `par_join()` to get a rayon `ParallelIterator`. 34 | for (pos, vel) in (&mut pos, &vel).join() { 35 | pos.0 += vel.0; 36 | } 37 | } 38 | } 39 | 40 | fn main() { 41 | // The `World` is our 42 | // container for components 43 | // and other resources. 44 | 45 | let mut world = World::new(); 46 | world.register::(); 47 | world.register::(); 48 | 49 | // An entity may or may not contain some component. 50 | 51 | world.create_entity().with(Vel(2.0)).with(Pos(0.0)).build(); 52 | world.create_entity().with(Vel(4.0)).with(Pos(1.6)).build(); 53 | world.create_entity().with(Vel(1.5)).with(Pos(5.4)).build(); 54 | 55 | // This entity does not have `Vel`, so it won't be dispatched. 56 | world.create_entity().with(Pos(2.0)).build(); 57 | 58 | // This builds an async dispatcher. 59 | // The third parameter of `add` specifies 60 | // logical dependencies on other systems. 61 | // Since we only have one, we don't depend on anything. 62 | // See the `full` example for dependencies. 63 | #[cfg(feature = "parallel")] 64 | { 65 | let mut dispatcher = DispatcherBuilder::new() 66 | .with(SysA, "sys_a", &[]) 67 | .build_async(world); 68 | 69 | // This dispatches all the systems in parallel and async. 70 | dispatcher.dispatch(); 71 | 72 | // Do something on the main thread 73 | 74 | dispatcher.wait(); 75 | } 76 | 77 | #[cfg(not(feature = "parallel"))] 78 | { 79 | eprintln!("The `async` example should be built with the `\"parallel\"` feature enabled."); 80 | 81 | let mut dispatcher = DispatcherBuilder::new().with(SysA, "sys_a", &[]).build(); 82 | 83 | dispatcher.setup(&mut world); 84 | 85 | dispatcher.dispatch(&mut world); 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | extern crate specs; 2 | 3 | use specs::prelude::*; 4 | 5 | // A component contains data which is associated with an entity. 6 | 7 | #[derive(Debug)] 8 | struct Vel(f32); 9 | 10 | impl Component for Vel { 11 | type Storage = VecStorage; 12 | } 13 | 14 | #[derive(Debug)] 15 | struct Pos(f32); 16 | 17 | impl Component for Pos { 18 | type Storage = VecStorage; 19 | } 20 | 21 | struct SysA; 22 | 23 | impl<'a> System<'a> for SysA { 24 | // These are the resources required for execution. 25 | // You can also define a struct and `#[derive(SystemData)]`, 26 | // see the `full` example. 27 | type SystemData = (WriteStorage<'a, Pos>, ReadStorage<'a, Vel>); 28 | 29 | fn run(&mut self, (mut pos, vel): Self::SystemData) { 30 | // The `.join()` combines multiple components, 31 | // so we only access those entities which have 32 | // both of them. 33 | // You could also use `par_join()` to get a rayon `ParallelIterator`. 34 | for (pos, vel) in (&mut pos, &vel).join() { 35 | pos.0 += vel.0; 36 | } 37 | } 38 | } 39 | 40 | fn main() { 41 | // The `World` is our 42 | // container for components 43 | // and other resources. 44 | 45 | let mut world = World::new(); 46 | 47 | // This builds a dispatcher. 48 | // The third parameter of `add` specifies 49 | // logical dependencies on other systems. 50 | // Since we only have one, we don't depend on anything. 51 | // See the `full` example for dependencies. 52 | let mut dispatcher = DispatcherBuilder::new().with(SysA, "sys_a", &[]).build(); 53 | 54 | // setup() must be called before creating any entity, it will register 55 | // all Components and Resources that Systems depend on 56 | dispatcher.setup(&mut world); 57 | 58 | // An entity may or may not contain some component. 59 | 60 | world.create_entity().with(Vel(2.0)).with(Pos(0.0)).build(); 61 | world.create_entity().with(Vel(4.0)).with(Pos(1.6)).build(); 62 | world.create_entity().with(Vel(1.5)).with(Pos(5.4)).build(); 63 | 64 | // This entity does not have `Vel`, so it won't be dispatched. 65 | world.create_entity().with(Pos(2.0)).build(); 66 | 67 | // This dispatches all the systems in parallel (but blocking). 68 | dispatcher.dispatch(&world); 69 | } 70 | -------------------------------------------------------------------------------- /examples/bitset.rs: -------------------------------------------------------------------------------- 1 | extern crate hibitset; 2 | extern crate specs; 3 | 4 | use hibitset::{BitSet, BitSetNot}; 5 | use specs::prelude::*; 6 | 7 | const COUNT: u32 = 100; 8 | 9 | fn main() { 10 | let mut every3 = BitSet::new(); 11 | for i in 0..COUNT { 12 | if i % 3 == 0 { 13 | every3.add(i); 14 | } 15 | } 16 | 17 | let mut every5 = BitSet::new(); 18 | for i in 0..COUNT { 19 | if i % 5 == 0 { 20 | every5.add(i); 21 | } 22 | } 23 | 24 | // over engineered fizzbuzz because why not 25 | let mut list: Vec = Vec::with_capacity(COUNT as usize); 26 | for id in 0..COUNT { 27 | list.push(format!("{}", id)); 28 | } 29 | 30 | for (id, _) in (&BitSetNot(&every3), &every5).join() { 31 | list[id as usize] = format!("fizz {}", id); 32 | } 33 | 34 | for (id, _) in (&BitSetNot(&every5), &every3).join() { 35 | list[id as usize] = format!("buzz {}", id); 36 | } 37 | 38 | for (id, _) in (&every3, &every5).join() { 39 | list[id as usize] = format!("fizzbuzz {}", id); 40 | } 41 | 42 | println!("{:#?}", list); 43 | } 44 | -------------------------------------------------------------------------------- /examples/lend_join.rs: -------------------------------------------------------------------------------- 1 | use specs::prelude::*; 2 | struct Pos(f32); 3 | 4 | impl Component for Pos { 5 | type Storage = VecStorage; 6 | } 7 | 8 | fn main() { 9 | let mut world = World::new(); 10 | 11 | world.register::(); 12 | 13 | let entity0 = world.create_entity().with(Pos(0.0)).build(); 14 | world.create_entity().with(Pos(1.6)).build(); 15 | world.create_entity().with(Pos(5.4)).build(); 16 | 17 | let mut pos = world.write_storage::(); 18 | let entities = world.entities(); 19 | 20 | // Unlike `join` the type return from `lend_join` does not implement 21 | // `Iterator`. Instead, a `next` method is provided that only allows one 22 | // element to be accessed at once. 23 | let mut lending = (&mut pos).lend_join(); 24 | 25 | // We copy the value out here so the borrow of `lending` is released. 26 | let a = lending.next().unwrap().0; 27 | // Here we keep the reference from `lending.next()` alive, so `lending` 28 | // remains exclusively borrowed for the lifetime of `b`. 29 | let b = lending.next().unwrap(); 30 | // This right fails to compile since `b` is used below: 31 | // let d = lending.next().unwrap(); 32 | b.0 = a; 33 | 34 | // Items can be iterated with `while let` loop: 35 | let mut lending = (&mut pos).lend_join(); 36 | while let Some(pos) = lending.next() { 37 | pos.0 *= 1.5; 38 | } 39 | 40 | // A `for_each` method is also available: 41 | (&mut pos).lend_join().for_each(|pos| { 42 | pos.0 += 1.0; 43 | }); 44 | 45 | // Finally, there is one bonus feature which `.join()` can't soundly provide. 46 | let mut lending = (&mut pos).lend_join(); 47 | // That is, there is a method to get the joined result for a particular 48 | // entity: 49 | if let Some(pos) = lending.get(entity0, &entities) { 50 | pos.0 += 5.0; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/ordered_track.rs: -------------------------------------------------------------------------------- 1 | extern crate hibitset; 2 | extern crate shrev; 3 | extern crate specs; 4 | 5 | use std::collections::HashMap; 6 | 7 | use specs::prelude::*; 8 | 9 | struct TrackedComponent(u64); 10 | 11 | impl Component for TrackedComponent { 12 | type Storage = FlaggedStorage; 13 | } 14 | 15 | #[derive(Default)] 16 | struct SysA { 17 | reader_id: Option>, 18 | cache: HashMap, 19 | } 20 | 21 | impl<'a> System<'a> for SysA { 22 | type SystemData = (Entities<'a>, ReadStorage<'a, TrackedComponent>); 23 | 24 | fn setup(&mut self, res: &mut World) { 25 | Self::SystemData::setup(res); 26 | self.reader_id = Some(WriteStorage::::fetch(&res).register_reader()); 27 | } 28 | 29 | fn run(&mut self, (entities, tracked): Self::SystemData) { 30 | let events = tracked 31 | .channel() 32 | .read(self.reader_id.as_mut().expect("ReaderId not found")); 33 | 34 | // These events are received in the same order they were operated on in the last 35 | // frame. However, be careful. Just because you received a 36 | // `Modified/Inserted` event does not mean that the entity at that index 37 | // has a component. To get the current state of the entity, you should replay 38 | // the events in order to see the final result of the component. Partial 39 | // iteration over the events might lead to weird bugs and issues. 40 | for event in events { 41 | match event { 42 | ComponentEvent::Modified(id) => { 43 | let entity = entities.entity(*id); 44 | if let Some(component) = tracked.get(entity) { 45 | // This is safe because it can only occur after an `Inserted` event, not a 46 | // `Removed` event. 47 | *self.cache.get_mut(id).unwrap() = (entity, component.0); 48 | println!("{:?} was changed to {:?}", entity, component.0); 49 | } else { 50 | println!( 51 | "{:?} was changed, but was removed before the next update.", 52 | entity 53 | ); 54 | } 55 | } 56 | ComponentEvent::Inserted(id) => { 57 | let entity = entities.entity(*id); 58 | if let Some(component) = tracked.get(entity) { 59 | self.cache.insert(*id, (entity, component.0)); 60 | println!("{:?} had {:?} inserted", entity, component.0); 61 | } else { 62 | println!( 63 | "{:?} had a component inserted, but was removed before the next update.", 64 | entity 65 | ); 66 | } 67 | } 68 | ComponentEvent::Removed(id) => { 69 | let entity = entities.entity(*id); 70 | self.cache.remove(id); 71 | println!("{:?} had its component removed", entity); 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | fn main() { 79 | let mut world = World::new(); 80 | 81 | let mut dispatcher = DispatcherBuilder::new() 82 | .with(SysA::default(), "sys_a", &[]) 83 | .build(); 84 | 85 | dispatcher.setup(&mut world); 86 | 87 | let e1 = world.create_entity().with(TrackedComponent(1)).build(); 88 | let e2 = world.create_entity().with(TrackedComponent(2)).build(); 89 | let e3 = world.create_entity().with(TrackedComponent(3)).build(); 90 | let e4 = world.create_entity().with(TrackedComponent(4)).build(); 91 | 92 | dispatcher.dispatch(&mut world); 93 | world.maintain(); 94 | 95 | { 96 | let mut tracked = world.write_storage::(); 97 | tracked.get_mut(e1).unwrap().0 = 0; 98 | tracked.get_mut(e2).unwrap().0 = 50; 99 | tracked.get_mut(e4).unwrap().0 *= 2; 100 | tracked.remove(e1); 101 | } 102 | 103 | dispatcher.dispatch(&mut world); 104 | world.maintain(); 105 | 106 | { 107 | let mut tracked = world.write_storage::(); 108 | 109 | // Note that any removal after a modification won't be seen in the next frame, 110 | // instead you will find no component or if a new component was inserted 111 | // right after it was inserted then then you will find the new inserted 112 | // component rather than the modified one from earlier. 113 | tracked.get_mut(e3).unwrap().0 = 20; 114 | tracked.remove(e3); 115 | tracked.insert(e3, TrackedComponent(10)).unwrap(); 116 | } 117 | 118 | dispatcher.dispatch(&mut world); 119 | world.maintain(); 120 | } 121 | -------------------------------------------------------------------------------- /examples/slices.rs: -------------------------------------------------------------------------------- 1 | extern crate specs; 2 | 3 | use specs::prelude::*; 4 | 5 | // A component contains data which is associated with an entity. 6 | 7 | #[derive(Debug)] 8 | struct Vel(f32); 9 | 10 | impl Component for Vel { 11 | type Storage = DefaultVecStorage; 12 | } 13 | 14 | impl Default for Vel { 15 | fn default() -> Self { 16 | Self(0.0) 17 | } 18 | } 19 | 20 | #[derive(Debug)] 21 | struct Pos(f32); 22 | 23 | impl Component for Pos { 24 | type Storage = DefaultVecStorage; 25 | } 26 | 27 | impl Default for Pos { 28 | fn default() -> Self { 29 | Self(0.0) 30 | } 31 | } 32 | 33 | struct SysA; 34 | 35 | impl<'a> System<'a> for SysA { 36 | // These are the resources required for execution. 37 | // You can also define a struct and `#[derive(SystemData)]`, 38 | // see the `full` example. 39 | type SystemData = (WriteStorage<'a, Pos>, ReadStorage<'a, Vel>); 40 | 41 | fn run(&mut self, (mut pos, vel): Self::SystemData) { 42 | // Both the `Pos` and `Vel` components use `DefaultVecStorage`, which supports 43 | // `as_slice()` and `as_mut_slice()`. This lets us access components without 44 | // indirection. 45 | let pos_slice = pos.as_mut_slice(); 46 | let vel_slice = vel.as_slice(); 47 | 48 | // Note that an entity which has position but not velocity will still have 49 | // an entry in both slices. `DefaultVecStorage` is sparse, and here is where 50 | // that matters: the storage has space for many entities, but not all of them 51 | // contain meaningful values. These slices may be a mix of present and absent 52 | // (`Default`) data. 53 | // 54 | // We could check the `mask()` before reading the velocity and updating the 55 | // position, and this is what `.join()` normally does. However, because: 56 | // 57 | // 1. `Vel` uses `DefaultVecStorage`, 58 | // 2. `Vel`'s default is 0.0, and 59 | // 3. `Pos` += 0.0 is a no-op, 60 | // 61 | // we can unconditionally add `Vel` to `Pos` without reading the `mask()`! 62 | // This results in a tight inner loop of known size, which is especially 63 | // suitable for SIMD, OpenCL, CUDA, and other accelerator technologies. 64 | // 65 | // Finally, note that `DefaultVecStorage` and `VecStorage` slice indices 66 | // always agree. If an entity is at location `i` in one `VecStorage`, it 67 | // will be at location `i` in every other `VecStorage`. (By contrast, 68 | // `DenseVecStorage` uses unpredictable indices and cannot be used in 69 | // this way.) We need only worry about handling slices of different 70 | // lengths. 71 | let len = pos_slice.len().min(vel_slice.len()); 72 | for i in 0..len { 73 | pos_slice[i].0 += vel_slice[i].0; 74 | } 75 | } 76 | } 77 | 78 | fn main() { 79 | // The `World` is our 80 | // container for components 81 | // and other resources. 82 | 83 | let mut world = World::new(); 84 | 85 | // This builds a dispatcher. 86 | // The third parameter of `add` specifies 87 | // logical dependencies on other systems. 88 | // Since we only have one, we don't depend on anything. 89 | // See the `full` example for dependencies. 90 | let mut dispatcher = DispatcherBuilder::new().with(SysA, "sys_a", &[]).build(); 91 | 92 | // setup() must be called before creating any entity, it will register 93 | // all Components and Resources that Systems depend on 94 | dispatcher.setup(&mut world); 95 | 96 | // An entity may or may not contain some component. 97 | 98 | world.create_entity().with(Vel(2.0)).with(Pos(0.0)).build(); 99 | world.create_entity().with(Vel(4.0)).with(Pos(1.6)).build(); 100 | world.create_entity().with(Vel(1.5)).with(Pos(5.4)).build(); 101 | 102 | // This entity does not have `Vel`, so it won't be dispatched. 103 | world.create_entity().with(Pos(2.0)).build(); 104 | 105 | // This dispatches all the systems in parallel (but blocking). 106 | dispatcher.dispatch(&world); 107 | } 108 | -------------------------------------------------------------------------------- /examples/track.rs: -------------------------------------------------------------------------------- 1 | extern crate hibitset; 2 | extern crate shrev; 3 | extern crate specs; 4 | 5 | use specs::prelude::*; 6 | 7 | struct TrackedComponent(u64); 8 | 9 | impl Component for TrackedComponent { 10 | type Storage = FlaggedStorage; 11 | } 12 | 13 | #[derive(Default)] 14 | struct SysA { 15 | reader_id: Option>, 16 | inserted: BitSet, 17 | modified: BitSet, 18 | removed: BitSet, 19 | } 20 | 21 | impl<'a> System<'a> for SysA { 22 | type SystemData = (Entities<'a>, ReadStorage<'a, TrackedComponent>); 23 | 24 | fn setup(&mut self, res: &mut World) { 25 | Self::SystemData::setup(res); 26 | self.reader_id = Some(WriteStorage::::fetch(&res).register_reader()); 27 | } 28 | 29 | fn run(&mut self, (entities, tracked): Self::SystemData) { 30 | self.modified.clear(); 31 | self.inserted.clear(); 32 | self.removed.clear(); 33 | 34 | let events = tracked 35 | .channel() 36 | .read(self.reader_id.as_mut().expect("ReaderId not found")); 37 | for event in events { 38 | match event { 39 | ComponentEvent::Modified(id) => { 40 | self.modified.add(*id); 41 | } 42 | ComponentEvent::Inserted(id) => { 43 | self.inserted.add(*id); 44 | } 45 | ComponentEvent::Removed(id) => { 46 | self.removed.add(*id); 47 | } 48 | } 49 | } 50 | 51 | for (entity, _tracked, _) in (&entities, &tracked, &self.modified).join() { 52 | println!("modified: {:?}", entity); 53 | } 54 | 55 | for (entity, _tracked, _) in (&entities, &tracked, &self.inserted).join() { 56 | println!("inserted: {:?}", entity); 57 | } 58 | 59 | for (entity, _tracked, _) in (&entities, &tracked, &self.removed).join() { 60 | println!("removed: {:?}", entity); 61 | } 62 | } 63 | } 64 | 65 | #[derive(Default)] 66 | struct SysB; 67 | impl<'a> System<'a> for SysB { 68 | type SystemData = (Entities<'a>, WriteStorage<'a, TrackedComponent>); 69 | 70 | fn run(&mut self, (entities, mut tracked): Self::SystemData) { 71 | for (entity, mut restricted) in (&entities, &mut tracked.restrict_mut()).join() { 72 | if entity.id() % 2 == 0 { 73 | let comp = restricted.get_mut(); 74 | comp.0 += 1; 75 | } 76 | } 77 | } 78 | } 79 | 80 | fn main() { 81 | let mut world = World::new(); 82 | 83 | let mut dispatcher = DispatcherBuilder::new() 84 | .with(SysA::default(), "sys_a", &[]) 85 | .with(SysB::default(), "sys_b", &[]) 86 | .build(); 87 | 88 | dispatcher.setup(&mut world); 89 | 90 | for _ in 0..50 { 91 | world.create_entity().with(TrackedComponent(0)).build(); 92 | } 93 | 94 | dispatcher.dispatch(&mut world); 95 | world.maintain(); 96 | 97 | let entities = (&world.entities(), &world.read_storage::()) 98 | .join() 99 | .map(|(e, _)| e) 100 | .collect::>(); 101 | world.delete_entities(&entities).unwrap(); 102 | 103 | for _ in 0..50 { 104 | world.create_entity().with(TrackedComponent(0)).build(); 105 | } 106 | 107 | dispatcher.dispatch(&mut world); 108 | world.maintain(); 109 | } 110 | -------------------------------------------------------------------------------- /miri.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Convenience script for running Miri, also the same one that the CI runs! 4 | 5 | set -e 6 | 7 | # use half the available threads since miri can be a bit memory hungry 8 | test_threads=$((($(nproc) - 1) / 2 + 1)) 9 | echo using $test_threads threads 10 | 11 | # filters out long running tests 12 | filter='not (test(100k) | test(map_test::wrap) | test(map_test::insert_same_key) | test(=mixed_create_merge)| test(=par_join_many_entities_and_systems) | test(=stillborn_entities))' 13 | echo "using filter: \"$filter\"" 14 | 15 | # Miri currently reports leaks in some tests so we disable that check 16 | # here (might be due to ptr-int-ptr in crossbeam-epoch so might be 17 | # resolved in future versions of that crate). 18 | # 19 | # crossbeam-epoch doesn't pass with stacked borrows so we need to use tree-borrows 20 | # https://github.com/crossbeam-rs/crossbeam/issues/545 21 | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-ignore-leaks -Zmiri-tree-borrows" \ 22 | cargo +nightly miri nextest run \ 23 | -E "$filter" \ 24 | --test-threads="$test_threads" \ 25 | # use nocapture or run miri directly to see warnings from miri 26 | #--nocapture 27 | 28 | # Run tests only available when parallel feature is disabled. 29 | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-ignore-leaks -Zmiri-tree-borrows" \ 30 | cargo +nightly miri nextest run \ 31 | --no-default-features \ 32 | -E "binary(no_parallel)" \ 33 | --test-threads="$test_threads" 34 | 35 | -------------------------------------------------------------------------------- /specs-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "specs-derive" 3 | version = "0.4.1" 4 | authors = ["Eyal Kalderon "] 5 | description = "Custom derive macro for Specs components" 6 | documentation = "https://docs.rs/specs-derive" 7 | repository = "https://github.com/slide-rs/specs/tree/master/specs-derive" 8 | keywords = ["gamedev", "parallel", "specs", "ecs", "derive"] 9 | license = "MIT/Apache-2.0" 10 | edition = "2018" 11 | 12 | [dependencies] 13 | proc-macro2 = "1.0.8" 14 | syn = "1.0.14" 15 | quote = "1.0.2" 16 | 17 | [lib] 18 | proc-macro = true 19 | -------------------------------------------------------------------------------- /specs-derive/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 The Specs Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /specs-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Implements the `#[derive(Component)]`, `#[derive(Saveload)]` macro and 2 | //! `#[component]` attribute for [Specs][sp]. 3 | //! 4 | //! [sp]: https://slide-rs.github.io/specs-website/ 5 | 6 | #![recursion_limit = "128"] 7 | 8 | extern crate proc_macro; 9 | extern crate proc_macro2; 10 | #[macro_use] 11 | extern crate quote; 12 | #[macro_use] 13 | extern crate syn; 14 | 15 | use proc_macro::TokenStream; 16 | use syn::{ 17 | parse::{Parse, ParseStream, Result}, 18 | DeriveInput, Path, PathArguments, 19 | }; 20 | 21 | mod impl_saveload; 22 | 23 | /// Custom derive macro for the `Component` trait. 24 | /// 25 | /// ## Example 26 | /// 27 | /// ```rust,ignore 28 | /// use specs::storage::VecStorage; 29 | /// 30 | /// #[derive(Component, Debug)] 31 | /// #[storage(VecStorage)] // This line is optional, defaults to `DenseVecStorage` 32 | /// struct Pos(f32, f32, f32); 33 | /// ``` 34 | /// 35 | /// When the type parameter is `` it can be omitted i.e.: 36 | /// 37 | ///```rust,ignore 38 | /// use specs::storage::VecStorage; 39 | /// 40 | /// #[derive(Component, Debug)] 41 | /// #[storage(VecStorage)] // Equals to #[storage(VecStorage)] 42 | /// struct Pos(f32, f32, f32); 43 | /// ``` 44 | #[proc_macro_derive(Component, attributes(storage))] 45 | pub fn component(input: TokenStream) -> TokenStream { 46 | let ast = syn::parse(input).unwrap(); 47 | let gen = impl_component(&ast); 48 | gen.into() 49 | } 50 | 51 | struct StorageAttribute { 52 | storage: Path, 53 | } 54 | 55 | impl Parse for StorageAttribute { 56 | fn parse(input: ParseStream) -> Result { 57 | let content; 58 | let _parenthesized_token = parenthesized!(content in input); 59 | 60 | Ok(StorageAttribute { 61 | storage: content.parse()?, 62 | }) 63 | } 64 | } 65 | 66 | fn impl_component(ast: &DeriveInput) -> proc_macro2::TokenStream { 67 | let name = &ast.ident; 68 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 69 | 70 | let storage = ast 71 | .attrs 72 | .iter() 73 | .find(|attr| attr.path.segments[0].ident == "storage") 74 | .map(|attr| { 75 | syn::parse2::(attr.tokens.clone()) 76 | .unwrap() 77 | .storage 78 | }) 79 | .unwrap_or_else(|| parse_quote!(DenseVecStorage)); 80 | 81 | let additional_generics = match storage.segments.last().unwrap().arguments { 82 | PathArguments::AngleBracketed(_) => quote!(), 83 | _ => quote!(), 84 | }; 85 | 86 | quote! { 87 | impl #impl_generics Component for #name #ty_generics #where_clause { 88 | type Storage = #storage #additional_generics; 89 | } 90 | } 91 | } 92 | 93 | /// Custom derive macro for the `ConvertSaveload` trait. 94 | /// 95 | /// Requires `Entity`, `ConvertSaveload`, `Marker` to be in a scope 96 | /// 97 | /// ## Example 98 | /// 99 | /// ```rust,ignore 100 | /// use specs::{Entity, saveload::{ConvertSaveload, Marker}}; 101 | /// 102 | /// #[derive(ConvertSaveload)] 103 | /// struct Target(Entity); 104 | /// ``` 105 | #[proc_macro_derive( 106 | ConvertSaveload, 107 | attributes(convert_save_load_attr, convert_save_load_skip_convert) 108 | )] 109 | pub fn saveload(input: TokenStream) -> TokenStream { 110 | use impl_saveload::impl_saveload; 111 | let mut ast = syn::parse(input).unwrap(); 112 | 113 | let gen = impl_saveload(&mut ast); 114 | gen.into() 115 | } 116 | -------------------------------------------------------------------------------- /src/bitset.rs: -------------------------------------------------------------------------------- 1 | //! Implementations and structures related to bitsets. 2 | //! 3 | //! Normally used for `Join`s and filtering entities. 4 | 5 | #![cfg_attr(rustfmt, rustfmt::skip)] 6 | 7 | use hibitset::{AtomicBitSet, BitSet, BitSetAnd, BitSetLike, BitSetNot, BitSetOr, BitSetXor}; 8 | 9 | #[nougat::gat(Type)] 10 | use crate::join::LendJoin; 11 | #[cfg(feature = "parallel")] 12 | use crate::join::ParJoin; 13 | use crate::join::{Join, RepeatableLendGet}; 14 | use crate::world::Index; 15 | 16 | macro_rules! define_bit_join { 17 | ( impl < ( $( $lifetime:tt )* ) ( $( $arg:ident ),* ) > for $bitset:ty ) => { 18 | // SAFETY: `get` just returns the provided `id` (`Self::Value` is `()` 19 | // and corresponds with any mask instance). 20 | #[nougat::gat] 21 | unsafe impl<$( $lifetime, )* $( $arg ),*> LendJoin for $bitset 22 | where $( $arg: BitSetLike ),* 23 | { 24 | type Type<'next> = Index; 25 | type Value = (); 26 | type Mask = $bitset; 27 | 28 | unsafe fn open(self) -> (Self::Mask, Self::Value) { 29 | (self, ()) 30 | } 31 | 32 | unsafe fn get<'next>(_: &'next mut Self::Value, id: Index) -> Self::Type<'next> 33 | 34 | { 35 | id 36 | } 37 | } 38 | 39 | // SAFETY: <$biset as LendJoin>::get does not rely on only being called 40 | // once with a particular ID 41 | unsafe impl<$( $lifetime, )* $( $arg ),*> RepeatableLendGet for $bitset 42 | where $( $arg: BitSetLike ),* {} 43 | 44 | // SAFETY: `get` just returns the provided `id` (`Self::Value` is `()` 45 | // and corresponds with any mask instance). 46 | unsafe impl<$( $lifetime, )* $( $arg ),*> Join for $bitset 47 | where $( $arg: BitSetLike ),* 48 | { 49 | type Type = Index; 50 | type Value = (); 51 | type Mask = $bitset; 52 | 53 | unsafe fn open(self) -> (Self::Mask, Self::Value) { 54 | (self, ()) 55 | } 56 | 57 | unsafe fn get(_: &mut Self::Value, id: Index) -> Self::Type { 58 | id 59 | } 60 | } 61 | 62 | // SAFETY: `get` is safe to call concurrently and just returns the 63 | // provided `id` (`Self::Value` is `()` and corresponds with any mask 64 | // instance). 65 | #[cfg(feature = "parallel")] 66 | unsafe impl<$( $lifetime, )* $( $arg ),*> ParJoin for $bitset 67 | where $( $arg: BitSetLike ),* 68 | { 69 | type Type = Index; 70 | type Value = (); 71 | type Mask = $bitset; 72 | 73 | unsafe fn open(self) -> (Self::Mask, Self::Value) { 74 | (self, ()) 75 | } 76 | 77 | unsafe fn get(_: &Self::Value, id: Index) -> Self::Type { 78 | id 79 | } 80 | } 81 | } 82 | } 83 | 84 | define_bit_join!(impl<()()> for BitSet); 85 | define_bit_join!(impl<('a)()> for &'a BitSet); 86 | define_bit_join!(impl<()()> for AtomicBitSet); 87 | define_bit_join!(impl<('a)()> for &'a AtomicBitSet); 88 | define_bit_join!(impl<()(A)> for BitSetNot); 89 | define_bit_join!(impl<('a)(A)> for &'a BitSetNot); 90 | define_bit_join!(impl<()(A, B)> for BitSetAnd); 91 | define_bit_join!(impl<('a)(A, B)> for &'a BitSetAnd); 92 | define_bit_join!(impl<()(A, B)> for BitSetOr); 93 | define_bit_join!(impl<('a)(A, B)> for &'a BitSetOr); 94 | define_bit_join!(impl<()(A, B)> for BitSetXor); 95 | define_bit_join!(impl<('a)()> for &'a dyn BitSetLike); 96 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Specs errors 2 | //! 3 | //! There are specific types for errors (e.g. `WrongGeneration`) 4 | //! and additionally one `Error` type that can represent them all. 5 | //! Each error in this module has an `Into` implementation. 6 | 7 | use std::{ 8 | convert::Infallible, 9 | error::Error as StdError, 10 | fmt::{Debug, Display, Formatter, Result as FmtResult}, 11 | }; 12 | 13 | use crate::world::{Entity, Generation}; 14 | 15 | /// A boxed error implementing `Debug`, `Display` and `Error`. 16 | pub struct BoxedErr(pub Box); 17 | 18 | impl BoxedErr { 19 | /// Creates a new boxed error. 20 | pub fn new(err: T) -> Self 21 | where 22 | T: StdError + Send + Sync + 'static, 23 | { 24 | BoxedErr(Box::new(err)) 25 | } 26 | } 27 | 28 | impl AsRef for BoxedErr { 29 | fn as_ref(&self) -> &(dyn StdError + 'static) { 30 | self.0.as_ref() 31 | } 32 | } 33 | 34 | impl Debug for BoxedErr { 35 | fn fmt(&self, f: &mut Formatter) -> FmtResult { 36 | write!(f, "{:}", self.0) 37 | } 38 | } 39 | 40 | impl Display for BoxedErr { 41 | fn fmt(&self, f: &mut Formatter) -> FmtResult { 42 | write!(f, "{}", self.as_ref()) 43 | } 44 | } 45 | 46 | impl StdError for BoxedErr {} 47 | 48 | /// The Specs error type. 49 | /// This is an enum which is able to represent 50 | /// all error types of this library. 51 | #[derive(Debug)] 52 | #[non_exhaustive] 53 | pub enum Error { 54 | /// A custom, boxed error. 55 | Custom(BoxedErr), 56 | /// Wrong generation error. 57 | WrongGeneration(WrongGeneration), 58 | } 59 | 60 | impl Display for Error { 61 | fn fmt(&self, f: &mut Formatter) -> FmtResult { 62 | match *self { 63 | Error::Custom(ref e) => write!(f, "Custom: {}", e), 64 | Error::WrongGeneration(ref e) => write!(f, "Wrong generation: {}", e), 65 | } 66 | } 67 | } 68 | 69 | impl From for Error { 70 | fn from(e: Infallible) -> Self { 71 | match e {} 72 | } 73 | } 74 | 75 | impl From for Error { 76 | fn from(e: WrongGeneration) -> Self { 77 | Error::WrongGeneration(e) 78 | } 79 | } 80 | 81 | impl StdError for Error { 82 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 83 | let e = match *self { 84 | Error::Custom(ref e) => e.as_ref(), 85 | Error::WrongGeneration(ref e) => e, 86 | }; 87 | 88 | Some(e) 89 | } 90 | } 91 | 92 | /// Wrong generation error. 93 | #[derive(Debug, PartialEq, Eq)] 94 | pub struct WrongGeneration { 95 | /// The action that failed because of the wrong generation. 96 | pub action: &'static str, 97 | /// The actual generation of this id. 98 | pub actual_gen: Generation, 99 | /// The entity that has been passed, containing 100 | /// the id and the invalid generation. 101 | pub entity: Entity, 102 | } 103 | 104 | impl Display for WrongGeneration { 105 | fn fmt(&self, f: &mut Formatter) -> FmtResult { 106 | write!( 107 | f, 108 | "Tried to {} entity {:?}, but the generation is no longer valid; it should be {:?}", 109 | self.action, self.entity, self.actual_gen 110 | ) 111 | } 112 | } 113 | 114 | impl StdError for WrongGeneration {} 115 | 116 | /// Reexport of `Infallible` for a smoother transition. 117 | #[deprecated = "Use std::convert::Infallible instead"] 118 | pub type NoError = Infallible; 119 | -------------------------------------------------------------------------------- /src/join/bit_and.rs: -------------------------------------------------------------------------------- 1 | use hibitset::{BitSetAnd, BitSetLike}; 2 | use tuple_utils::Split; 3 | 4 | /// `BitAnd` is a helper method to & bitsets together resulting in a tree. 5 | pub trait BitAnd { 6 | /// The combined bitsets. 7 | type Value: BitSetLike; 8 | /// Combines `Self` into a single `BitSetLike` through `BitSetAnd`. 9 | fn and(self) -> Self::Value; 10 | } 11 | 12 | /// This needs to be special cased 13 | impl BitAnd for (A,) 14 | where 15 | A: BitSetLike, 16 | { 17 | type Value = A; 18 | 19 | fn and(self) -> Self::Value { 20 | self.0 21 | } 22 | } 23 | 24 | macro_rules! bitset_and { 25 | // use variables to indicate the arity of the tuple 26 | ($($from:ident),*) => { 27 | impl<$($from),*> BitAnd for ($($from),*) 28 | where $($from: BitSetLike),* 29 | { 30 | type Value = BitSetAnd< 31 | <::Left as BitAnd>::Value, 32 | <::Right as BitAnd>::Value 33 | >; 34 | 35 | fn and(self) -> Self::Value { 36 | let (l, r) = self.split(); 37 | BitSetAnd(l.and(), r.and()) 38 | } 39 | } 40 | } 41 | } 42 | 43 | bitset_and! {A, B} 44 | bitset_and! {A, B, C} 45 | bitset_and! {A, B, C, D} 46 | bitset_and! {A, B, C, D, E} 47 | bitset_and! {A, B, C, D, E, F} 48 | bitset_and! {A, B, C, D, E, F, G} 49 | bitset_and! {A, B, C, D, E, F, G, H} 50 | bitset_and! {A, B, C, D, E, F, G, H, I} 51 | bitset_and! {A, B, C, D, E, F, G, H, I, J} 52 | bitset_and! {A, B, C, D, E, F, G, H, I, J, K} 53 | bitset_and! {A, B, C, D, E, F, G, H, I, J, K, L} 54 | bitset_and! {A, B, C, D, E, F, G, H, I, J, K, L, M} 55 | bitset_and! {A, B, C, D, E, F, G, H, I, J, K, L, M, N} 56 | bitset_and! {A, B, C, D, E, F, G, H, I, J, K, L, M, N, O} 57 | bitset_and! {A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P} 58 | -------------------------------------------------------------------------------- /src/join/maybe.rs: -------------------------------------------------------------------------------- 1 | #[nougat::gat(Type)] 2 | use super::LendJoin; 3 | #[cfg(feature = "parallel")] 4 | use super::ParJoin; 5 | use super::{Join, RepeatableLendGet}; 6 | use hibitset::{BitSetAll, BitSetLike}; 7 | 8 | use crate::world::Index; 9 | 10 | /// Returns a structure that implements `Join`/`LendJoin`/`MaybeJoin` if the 11 | /// contained `T` does and that yields all indices, returning `None` for all 12 | /// missing elements and `Some(T)` for found elements. 13 | /// 14 | /// For usage see [`LendJoin::maybe()`](LendJoin::maybe). 15 | /// 16 | /// WARNING: Do not have a join of only `MaybeJoin`s. Otherwise the join will 17 | /// iterate over every single index of the bitset. If you want a join with 18 | /// all `MaybeJoin`s, add an `EntitiesRes` to the join as well to bound the 19 | /// join to all entities that are alive. 20 | pub struct MaybeJoin(pub J); 21 | 22 | // SAFETY: We return a mask containing all items, but check the original mask in 23 | // the `get` implementation. Iterating the mask does not repeat indices. 24 | #[nougat::gat] 25 | unsafe impl LendJoin for MaybeJoin 26 | where 27 | T: LendJoin, 28 | { 29 | type Mask = BitSetAll; 30 | type Type<'next> = Option<::Type<'next>>; 31 | type Value = (::Mask, ::Value); 32 | 33 | unsafe fn open(self) -> (Self::Mask, Self::Value) { 34 | // SAFETY: While we do expose the mask and the values and therefore 35 | // would allow swapping them, this method is `unsafe` and relies on the 36 | // same invariants. 37 | let (mask, value) = unsafe { self.0.open() }; 38 | (BitSetAll, (mask, value)) 39 | } 40 | 41 | unsafe fn get<'next>((mask, value): &'next mut Self::Value, id: Index) -> Self::Type<'next> { 42 | if mask.contains(id) { 43 | // SAFETY: The mask was just checked for `id`. Requirement to not 44 | // call with the same ID more than once (unless `RepeatableLendGet` 45 | // is implemented) is passed to the caller. 46 | Some(unsafe { ::get(value, id) }) 47 | } else { 48 | None 49 | } 50 | } 51 | 52 | #[inline] 53 | fn is_unconstrained() -> bool { 54 | true 55 | } 56 | } 57 | 58 | // SAFETY: ::get does not rely on only being called once 59 | // with a particular ID. 60 | unsafe impl RepeatableLendGet for MaybeJoin where T: RepeatableLendGet {} 61 | 62 | // SAFETY: We return a mask containing all items, but check the original mask in 63 | // the `get` implementation. Iterating the mask does not repeat indices. 64 | unsafe impl Join for MaybeJoin 65 | where 66 | T: Join, 67 | { 68 | type Mask = BitSetAll; 69 | type Type = Option<::Type>; 70 | type Value = (::Mask, ::Value); 71 | 72 | unsafe fn open(self) -> (Self::Mask, Self::Value) { 73 | // SAFETY: While we do expose the mask and the values and therefore 74 | // would allow swapping them, this method is `unsafe` and relies on the 75 | // same invariants. 76 | let (mask, value) = unsafe { self.0.open() }; 77 | (BitSetAll, (mask, value)) 78 | } 79 | 80 | unsafe fn get((mask, value): &mut Self::Value, id: Index) -> Self::Type { 81 | if mask.contains(id) { 82 | // SAFETY: The mask was just checked for `id`. This has the same 83 | // requirements on the caller to only call with the same `id` once. 84 | Some(unsafe { ::get(value, id) }) 85 | } else { 86 | None 87 | } 88 | } 89 | 90 | #[inline] 91 | fn is_unconstrained() -> bool { 92 | true 93 | } 94 | } 95 | 96 | // SAFETY: This is safe as long as `T` implements `ParJoin` safely. The `get` 97 | // implementation here makes no assumptions about being called from a single 98 | // thread. 99 | // 100 | // We return a mask containing all items, but check the original mask in 101 | // the `get` implementation. Iterating the mask does not repeat indices. 102 | #[cfg(feature = "parallel")] 103 | unsafe impl ParJoin for MaybeJoin 104 | where 105 | T: ParJoin, 106 | { 107 | type Mask = BitSetAll; 108 | type Type = Option<::Type>; 109 | type Value = (::Mask, ::Value); 110 | 111 | unsafe fn open(self) -> (Self::Mask, Self::Value) { 112 | // SAFETY: While we do expose the mask and the values and therefore 113 | // would allow swapping them, this method is `unsafe` and relies on the 114 | // same invariants. 115 | let (mask, value) = unsafe { self.0.open() }; 116 | (BitSetAll, (mask, value)) 117 | } 118 | 119 | unsafe fn get((mask, value): &Self::Value, id: Index) -> Self::Type { 120 | if mask.contains(id) { 121 | // SAFETY: The mask was just checked for `id`. This has the same 122 | // requirements on the caller to not call with the same `id` until 123 | // the previous value is no longer in use. 124 | Some(unsafe { ::get(value, id) }) 125 | } else { 126 | None 127 | } 128 | } 129 | 130 | #[inline] 131 | fn is_unconstrained() -> bool { 132 | true 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/join/par_join.rs: -------------------------------------------------------------------------------- 1 | use hibitset::{BitProducer, BitSetLike}; 2 | use rayon::iter::{ 3 | plumbing::{bridge_unindexed, Folder, UnindexedConsumer, UnindexedProducer}, 4 | ParallelIterator, 5 | }; 6 | 7 | use crate::world::Index; 8 | 9 | /// The purpose of the `ParJoin` trait is to provide a way 10 | /// to access multiple storages in parallel at the same time with 11 | /// the merged bit set. 12 | /// 13 | /// # Safety 14 | /// 15 | /// `ParJoin::get` must be callable from multiple threads, simultaneously. 16 | /// 17 | /// The `Self::Mask` value returned with the `Self::Value` must correspond such 18 | /// that it is safe to retrieve items from `Self::Value` whose presence is 19 | /// indicated in the mask. As part of this, `BitSetLike::iter` must not produce 20 | /// an iterator that repeats an `Index` value. 21 | pub unsafe trait ParJoin { 22 | /// Type of joined components. 23 | type Type; 24 | /// Type of joined storages. 25 | type Value; 26 | /// Type of joined bit mask. 27 | type Mask: BitSetLike; 28 | 29 | /// Create a joined parallel iterator over the contents. 30 | fn par_join(self) -> JoinParIter 31 | where 32 | Self: Sized, 33 | { 34 | if Self::is_unconstrained() { 35 | log::warn!( 36 | "`ParJoin` possibly iterating through all indices, \ 37 | you might've made a join with all `MaybeJoin`s, \ 38 | which is unbounded in length." 39 | ); 40 | } 41 | 42 | JoinParIter(self) 43 | } 44 | 45 | /// Open this join by returning the mask and the storages. 46 | /// 47 | /// # Safety 48 | /// 49 | /// This is unsafe because implementations of this trait can permit the 50 | /// `Value` to be mutated independently of the `Mask`. If the `Mask` does 51 | /// not correctly report the status of the `Value` then illegal memory 52 | /// access can occur. 53 | unsafe fn open(self) -> (Self::Mask, Self::Value); 54 | 55 | /// Get a joined component value by a given index. 56 | /// 57 | /// # Safety 58 | /// 59 | /// * A call to `get` must be preceded by a check if `id` is part of 60 | /// `Self::Mask`. 61 | /// * The value returned from this method must no longer be alive before 62 | /// subsequent calls with the same `id`. 63 | unsafe fn get(value: &Self::Value, id: Index) -> Self::Type; 64 | 65 | /// If this `LendJoin` typically returns all indices in the mask, then 66 | /// iterating over only it or combined with other joins that are also 67 | /// dangerous will cause the `JoinLendIter` to go through all indices which 68 | /// is usually not what is wanted and will kill performance. 69 | #[inline] 70 | fn is_unconstrained() -> bool { 71 | false 72 | } 73 | } 74 | 75 | /// `JoinParIter` is a `ParallelIterator` over a group of storages. 76 | #[must_use] 77 | pub struct JoinParIter(J); 78 | 79 | impl ParallelIterator for JoinParIter 80 | where 81 | J: ParJoin + Send, 82 | J::Mask: Send + Sync, 83 | J::Type: Send, 84 | J::Value: Send + Sync, 85 | { 86 | type Item = J::Type; 87 | 88 | fn drive_unindexed(self, consumer: C) -> C::Result 89 | where 90 | C: UnindexedConsumer, 91 | { 92 | // SAFETY: `keys` and `values` are not exposed outside this module and 93 | // we only use `values` for calling `ParJoin::get`. 94 | let (keys, values) = unsafe { self.0.open() }; 95 | // Create a bit producer which splits on up to three levels 96 | let producer = BitProducer((&keys).iter(), 3); 97 | 98 | bridge_unindexed(JoinProducer::::new(producer, &values), consumer) 99 | } 100 | } 101 | 102 | struct JoinProducer<'a, J> 103 | where 104 | J: ParJoin + Send, 105 | J::Mask: Send + Sync + 'a, 106 | J::Type: Send, 107 | J::Value: Send + Sync + 'a, 108 | { 109 | keys: BitProducer<'a, J::Mask>, 110 | values: &'a J::Value, 111 | } 112 | 113 | impl<'a, J> JoinProducer<'a, J> 114 | where 115 | J: ParJoin + Send, 116 | J::Type: Send, 117 | J::Value: 'a + Send + Sync, 118 | J::Mask: 'a + Send + Sync, 119 | { 120 | fn new(keys: BitProducer<'a, J::Mask>, values: &'a J::Value) -> Self { 121 | JoinProducer { keys, values } 122 | } 123 | } 124 | 125 | impl<'a, J> UnindexedProducer for JoinProducer<'a, J> 126 | where 127 | J: ParJoin + Send, 128 | J::Type: Send, 129 | J::Value: 'a + Send + Sync, 130 | J::Mask: 'a + Send + Sync, 131 | { 132 | type Item = J::Type; 133 | 134 | fn split(self) -> (Self, Option) { 135 | let (cur, other) = self.keys.split(); 136 | let values = self.values; 137 | let first = JoinProducer::new(cur, values); 138 | let second = other.map(|o| JoinProducer::new(o, values)); 139 | 140 | (first, second) 141 | } 142 | 143 | fn fold_with(self, folder: F) -> F 144 | where 145 | F: Folder, 146 | { 147 | let JoinProducer { values, keys, .. } = self; 148 | // SAFETY: `idx` is obtained from the `Mask` returned by 149 | // `ParJoin::open`. The indices here are guaranteed to be distinct 150 | // because of the fact that the bit set is split and because `ParJoin` 151 | // requires that the bit set iterator doesn't repeat indices. 152 | let iter = keys.0.map(|idx| unsafe { J::get(values, idx) }); 153 | 154 | folder.consume_iter(iter) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Prelude module 2 | //! 3 | //! Contains all of the most common traits, structures, 4 | 5 | pub use crate::join::Join; 6 | #[nougat::gat(Type)] 7 | pub use crate::join::LendJoin; 8 | #[cfg(feature = "parallel")] 9 | pub use crate::join::ParJoin; 10 | pub use hibitset::BitSet; 11 | pub use shred::{ 12 | Accessor, Dispatcher, DispatcherBuilder, Read, ReadExpect, Resource, ResourceId, RunNow, 13 | StaticAccessor, System, SystemData, World, Write, WriteExpect, 14 | }; 15 | pub use shrev::ReaderId; 16 | 17 | #[cfg(feature = "parallel")] 18 | pub use rayon::iter::ParallelIterator; 19 | #[cfg(feature = "parallel")] 20 | pub use shred::AsyncDispatcher; 21 | 22 | pub use crate::{ 23 | changeset::ChangeSet, 24 | storage::{ 25 | ComponentEvent, DefaultVecStorage, DenseVecStorage, FlaggedStorage, HashMapStorage, 26 | NullStorage, ReadStorage, Storage, Tracked, VecStorage, WriteStorage, 27 | }, 28 | world::{Builder, Component, Entities, Entity, EntityBuilder, LazyUpdate, WorldExt}, 29 | }; 30 | -------------------------------------------------------------------------------- /src/saveload/mod.rs: -------------------------------------------------------------------------------- 1 | //! Save and load entities from various formats with serde. 2 | //! 3 | //! ## `WorldSerialize` / `WorldDeserialize` 4 | //! 5 | //! This module provides two `SystemData` implementors: 6 | //! 7 | //! * `SerializeComponents` and 8 | //! * `DeserializeComponents` 9 | //! 10 | //! Reading those makes it very easy to serialize or deserialize 11 | //! components. 12 | //! 13 | //! `SerializeComponents` implements `Serialize` and `DeserializeComponents` 14 | //! implements `DeserializeOwned`, so serializing / deserializing should be very 15 | //! easy. 16 | //! 17 | //! ## Markers 18 | //! 19 | //! Because you usually don't want to serialize everything, we use 20 | //! markers to say which entities we're interested in. However, these markers 21 | //! aren't just boolean values; we also have id spaces which allow us 22 | //! to identify entities even if local ids are different. The allocation 23 | //! of these ids is what `MarkerAllocator`s are responsible for. For an example, 24 | //! see the docs for the `Marker` trait. 25 | 26 | use std::convert::Infallible; 27 | 28 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 29 | 30 | use crate::world::Entity; 31 | 32 | mod de; 33 | mod marker; 34 | mod ser; 35 | #[cfg(test)] 36 | mod tests; 37 | #[cfg(feature = "uuid_entity")] 38 | mod uuid; 39 | 40 | #[cfg(feature = "uuid_entity")] 41 | pub use self::uuid::{UuidMarker, UuidMarkerAllocator}; 42 | pub use self::{ 43 | de::DeserializeComponents, 44 | marker::{MarkedBuilder, Marker, MarkerAllocator, SimpleMarker, SimpleMarkerAllocator}, 45 | ser::SerializeComponents, 46 | }; 47 | 48 | /// A struct used for deserializing entity data. 49 | #[derive(Serialize, Deserialize)] 50 | pub struct EntityData { 51 | /// The marker the entity was mapped to. 52 | pub marker: M, 53 | /// The components associated with an entity. 54 | pub components: D, 55 | } 56 | 57 | /// Converts a data type (usually a [`Component`]) into its serializable form 58 | /// and back to actual data from it's deserialized form. 59 | /// 60 | /// This is automatically implemented for any type that is 61 | /// [`Clone`], [`Serialize`] and [`DeserializeOwned`]. 62 | /// 63 | /// Implementing this yourself is usually only needed if you 64 | /// have a component that points to another [`Entity`], or has a field which 65 | /// does, and you wish to [`Serialize`] it. 66 | /// 67 | /// *Note*: if you're using `specs_derive` 68 | /// you can use `#[derive(Saveload)]` to automatically derive this. 69 | /// 70 | /// You must add the `derive` to any type that your component holds which does 71 | /// not auto-implement this traits, including the component itself (similar to 72 | /// how normal [`Serialize`] and [`Deserialize`] work). 73 | /// 74 | /// [`Component`]: ../trait.Component.html 75 | /// [`Entity`]: ../struct.Entity.html 76 | /// [`Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html 77 | /// [`Deserialize`]: https://docs.serde.rs/serde/trait.Deserialize.html 78 | /// [`DeserializeOwned`]: https://docs.serde.rs/serde/de/trait.DeserializeOwned.html 79 | /// 80 | /// # Example 81 | /// 82 | /// ```rust 83 | /// # extern crate specs; 84 | /// # #[macro_use] extern crate serde; 85 | /// use serde::{Deserialize, Serialize}; 86 | /// use specs::{ 87 | /// prelude::*, 88 | /// saveload::{ConvertSaveload, Marker}, 89 | /// }; 90 | /// use std::convert::Infallible; 91 | /// 92 | /// struct Target(Entity); 93 | /// 94 | /// impl Component for Target { 95 | /// type Storage = VecStorage; 96 | /// } 97 | /// 98 | /// // We need a matching "data" struct to hold our 99 | /// // marker. In general, you just need a single struct 100 | /// // per component you want to make `Serialize`/`Deserialize` with each 101 | /// // instance of `Entity` replaced with a generic "M". 102 | /// #[derive(Serialize, Deserialize)] 103 | /// struct TargetData(M); 104 | /// 105 | /// impl ConvertSaveload for Target 106 | /// where 107 | /// for<'de> M: Deserialize<'de>, 108 | /// { 109 | /// type Data = TargetData; 110 | /// type Error = Infallible; 111 | /// 112 | /// fn convert_into(&self, mut ids: F) -> Result 113 | /// where 114 | /// F: FnMut(Entity) -> Option, 115 | /// { 116 | /// let marker = ids(self.0).unwrap(); 117 | /// Ok(TargetData(marker)) 118 | /// } 119 | /// 120 | /// fn convert_from(data: Self::Data, mut ids: F) -> Result 121 | /// where 122 | /// F: FnMut(M) -> Option, 123 | /// { 124 | /// let entity = ids(data.0).unwrap(); 125 | /// Ok(Target(entity)) 126 | /// } 127 | /// } 128 | /// ``` 129 | pub trait ConvertSaveload: Sized { 130 | /// (De)Serializable data representation for data type 131 | type Data: Serialize + DeserializeOwned; 132 | 133 | /// Error may occur during serialization or deserialization of component 134 | type Error; 135 | 136 | /// Convert this data from a deserializable form (`Data`) using 137 | /// entity to marker mapping function 138 | fn convert_from(data: Self::Data, ids: F) -> Result 139 | where 140 | F: FnMut(M) -> Option; 141 | 142 | /// Convert this data type into serializable form (`Data`) using 143 | /// entity to marker mapping function 144 | fn convert_into(&self, ids: F) -> Result 145 | where 146 | F: FnMut(Entity) -> Option; 147 | } 148 | 149 | impl ConvertSaveload for C 150 | where 151 | C: Clone + Serialize + DeserializeOwned, 152 | { 153 | type Data = Self; 154 | type Error = Infallible; 155 | 156 | fn convert_into(&self, _: F) -> Result 157 | where 158 | F: FnMut(Entity) -> Option, 159 | { 160 | Ok(self.clone()) 161 | } 162 | 163 | fn convert_from(data: Self::Data, _: F) -> Result 164 | where 165 | F: FnMut(M) -> Option, 166 | { 167 | Ok(data) 168 | } 169 | } 170 | 171 | impl ConvertSaveload for Entity 172 | where 173 | M: Serialize + DeserializeOwned, 174 | { 175 | type Data = M; 176 | type Error = Infallible; 177 | 178 | fn convert_into(&self, mut func: F) -> Result 179 | where 180 | F: FnMut(Entity) -> Option, 181 | { 182 | Ok(func(*self).unwrap()) 183 | } 184 | 185 | fn convert_from(data: Self::Data, mut func: F) -> Result 186 | where 187 | F: FnMut(M) -> Option, 188 | { 189 | Ok(func(data).unwrap()) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/saveload/ser.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use serde::ser::{self, Serialize, SerializeSeq, Serializer}; 4 | 5 | use super::ConvertSaveload; 6 | use crate::{ 7 | join::Join, 8 | saveload::{ 9 | marker::{Marker, MarkerAllocator}, 10 | EntityData, 11 | }, 12 | storage::{GenericReadStorage, ReadStorage, WriteStorage}, 13 | world::{Component, EntitiesRes, Entity}, 14 | }; 15 | 16 | /// A trait which allows to serialize entities and their components. 17 | pub trait SerializeComponents 18 | where 19 | M: Marker, 20 | { 21 | /// The data representation of the components. 22 | type Data: Serialize; 23 | 24 | /// Serialize the components of a single entity using a entity -> marker 25 | /// mapping. 26 | fn serialize_entity(&self, entity: Entity, ids: F) -> Result 27 | where 28 | F: FnMut(Entity) -> Option; 29 | 30 | /// Serialize components from specified storages 31 | /// of all marked entities with provided serializer. 32 | /// When the component gets serialized the closure passed 33 | /// in `ids` argument returns `None` for unmarked `Entity`. 34 | /// In this case serialization of this component may perform workaround or 35 | /// fail. So the function doesn't recursively mark referenced entities. 36 | /// For recursive marking see `serialize_recursive` 37 | fn serialize( 38 | &self, 39 | entities: &EntitiesRes, 40 | markers: &ReadStorage, 41 | serializer: S, 42 | ) -> Result 43 | where 44 | E: Display, 45 | S: Serializer, 46 | { 47 | let count = (entities, markers).join().count(); 48 | let mut serseq = serializer.serialize_seq(Some(count))?; 49 | let ids = |entity| -> Option { markers.get(entity).cloned() }; 50 | for (entity, marker) in (entities, markers).join() { 51 | serseq.serialize_element(&EntityData:: { 52 | marker: marker.clone(), 53 | components: self 54 | .serialize_entity(entity, &ids) 55 | .map_err(ser::Error::custom)?, 56 | })?; 57 | } 58 | serseq.end() 59 | } 60 | 61 | /// Serialize components from specified storages 62 | /// of all marked entities with provided serializer. 63 | /// When the component gets serialized the closure passed 64 | /// in `ids` argument marks unmarked `Entity` (the marker of which was 65 | /// requested) and it will get serialized recursively. 66 | /// For serializing without such recursion see `serialize` function. 67 | fn serialize_recursive( 68 | &self, 69 | entities: &EntitiesRes, 70 | markers: &mut WriteStorage, 71 | allocator: &mut M::Allocator, 72 | serializer: S, 73 | ) -> Result 74 | where 75 | E: Display, 76 | M: Marker, 77 | S: Serializer, 78 | { 79 | let mut serseq = serializer.serialize_seq(None)?; 80 | let mut to_serialize: Vec<(Entity, M)> = (entities, &*markers) 81 | .join() 82 | .map(|(e, m)| (e, m.clone())) 83 | .collect(); 84 | while !to_serialize.is_empty() { 85 | let mut add = vec![]; 86 | { 87 | let mut ids = |entity| -> Option { 88 | if let Some((marker, added)) = allocator.mark(entity, markers) { 89 | if added { 90 | add.push((entity, marker.clone())); 91 | } 92 | Some(marker.clone()) 93 | } else { 94 | None 95 | } 96 | }; 97 | for (entity, marker) in to_serialize { 98 | serseq.serialize_element(&EntityData:: { 99 | marker, 100 | components: self 101 | .serialize_entity(entity, &mut ids) 102 | .map_err(ser::Error::custom)?, 103 | })?; 104 | } 105 | } 106 | to_serialize = add; 107 | } 108 | serseq.end() 109 | } 110 | } 111 | 112 | macro_rules! serialize_components { 113 | ($($comp:ident => $sto:ident,)*) => { 114 | impl<'a, E, M, $($comp,)* $($sto,)*> SerializeComponents for ($($sto,)*) 115 | where 116 | M: Marker, 117 | $( 118 | $sto: GenericReadStorage, 119 | $comp: ConvertSaveload + Component, 120 | E: From<<$comp as ConvertSaveload>::Error>, 121 | )* 122 | { 123 | type Data = ($(Option<$comp::Data>,)*); 124 | 125 | #[allow(unused)] 126 | fn serialize_entity(&self, entity: Entity, mut ids: F) -> Result 127 | where 128 | F: FnMut(Entity) -> Option 129 | { 130 | #[allow(bad_style)] 131 | let ($(ref $comp,)*) = *self; 132 | 133 | Ok(($( 134 | $comp.get(entity).map(|c| c.convert_into(&mut ids).map(Some)).unwrap_or(Ok(None))?, 135 | )*)) 136 | } 137 | } 138 | 139 | serialize_components!(@pop $($comp => $sto,)*); 140 | }; 141 | (@pop) => {}; 142 | (@pop $head0:ident => $head1:ident, $($tail0:ident => $tail1:ident,)*) => { 143 | serialize_components!($($tail0 => $tail1,)*); 144 | }; 145 | } 146 | 147 | serialize_components!( 148 | CA => SA, 149 | CB => SB, 150 | CC => SC, 151 | CD => SD, 152 | CE => SE, 153 | CF => SF, 154 | CG => SG, 155 | CH => SH, 156 | CI => SI, 157 | CJ => SJ, 158 | CK => SK, 159 | CL => SL, 160 | CN => SN, 161 | CM => SM, 162 | CO => SO, 163 | CP => SP, 164 | ); 165 | -------------------------------------------------------------------------------- /src/saveload/uuid.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use uuid::Uuid; 5 | 6 | use crate::{ 7 | join::Join, 8 | saveload::{Marker, MarkerAllocator}, 9 | storage::{ReadStorage, VecStorage}, 10 | world::{Component, EntitiesRes, Entity}, 11 | }; 12 | 13 | /// Basic marker uuid implementation usable for saving and loading. 14 | #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] 15 | pub struct UuidMarker { 16 | uuid: Uuid, 17 | } 18 | 19 | impl Component for UuidMarker { 20 | type Storage = VecStorage; 21 | } 22 | 23 | impl Marker for UuidMarker { 24 | type Allocator = UuidMarkerAllocator; 25 | type Identifier = Uuid; 26 | 27 | fn id(&self) -> Uuid { 28 | self.uuid() 29 | } 30 | } 31 | 32 | impl UuidMarker { 33 | /// Creates a new `UuidMarker` Component from the specified uuid. 34 | pub fn new(uuid: Uuid) -> Self { 35 | UuidMarker { uuid } 36 | } 37 | 38 | /// Creates a new `UuidMarker` Component with a random uuid. 39 | pub fn new_random() -> Self { 40 | let uuid = Uuid::new_v4(); 41 | UuidMarker { uuid } 42 | } 43 | 44 | /// Get the current uuid. 45 | pub fn uuid(&self) -> Uuid { 46 | self.uuid 47 | } 48 | } 49 | 50 | /// Basic marker allocator for uuid. 51 | #[derive(Clone, Debug)] 52 | pub struct UuidMarkerAllocator { 53 | mapping: HashMap, 54 | } 55 | 56 | impl Default for UuidMarkerAllocator { 57 | fn default() -> Self { 58 | UuidMarkerAllocator::new() 59 | } 60 | } 61 | 62 | impl UuidMarkerAllocator { 63 | /// Create new `UuidMarkerAllocator` which will yield `UuidMarker`s. 64 | pub fn new() -> Self { 65 | Self { 66 | mapping: HashMap::new(), 67 | } 68 | } 69 | } 70 | 71 | impl MarkerAllocator for UuidMarkerAllocator { 72 | fn allocate(&mut self, entity: Entity, id: Option) -> UuidMarker { 73 | let marker = if let Some(id) = id { 74 | UuidMarker::new(id) 75 | } else { 76 | UuidMarker::new_random() 77 | }; 78 | self.mapping.insert(marker.uuid(), entity); 79 | 80 | marker 81 | } 82 | 83 | fn retrieve_entity_internal(&self, id: Uuid) -> Option { 84 | self.mapping.get(&id).cloned() 85 | } 86 | 87 | fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage) { 88 | // FIXME: may be too slow 89 | self.mapping = (entities, storage) 90 | .join() 91 | .map(|(e, m)| (m.uuid(), e)) 92 | .collect(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/storage/deref_flagged.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | marker::PhantomData, 3 | ops::{Deref, DerefMut}, 4 | }; 5 | 6 | use hibitset::BitSetLike; 7 | 8 | use crate::{ 9 | storage::{ 10 | AccessMut, ComponentEvent, DenseVecStorage, Tracked, TryDefault, UnprotectedStorage, 11 | }, 12 | world::{Component, Index}, 13 | }; 14 | 15 | use shrev::EventChannel; 16 | 17 | /// Wrapper storage that tracks modifications, insertions, and removals of 18 | /// components through an `EventChannel`, in a similar manner to 19 | /// `FlaggedStorage`. 20 | /// 21 | /// Unlike `FlaggedStorage`, this storage uses a wrapper type for mutable 22 | /// accesses that only emits modification events when the component is actually 23 | /// used mutably. This means that simply performing a mutable join or calling 24 | /// `WriteStorage::get_mut` will not, by itself, trigger a modification event. 25 | pub struct DerefFlaggedStorage> { 26 | channel: EventChannel, 27 | storage: T, 28 | #[cfg(feature = "storage-event-control")] 29 | event_emission: bool, 30 | phantom: PhantomData, 31 | } 32 | 33 | impl DerefFlaggedStorage { 34 | #[cfg(feature = "storage-event-control")] 35 | fn emit_event(&self) -> bool { 36 | self.event_emission 37 | } 38 | 39 | #[cfg(not(feature = "storage-event-control"))] 40 | fn emit_event(&self) -> bool { 41 | true 42 | } 43 | } 44 | 45 | impl Default for DerefFlaggedStorage 46 | where 47 | T: TryDefault, 48 | { 49 | fn default() -> Self { 50 | Self { 51 | channel: EventChannel::::default(), 52 | storage: T::unwrap_default(), 53 | #[cfg(feature = "storage-event-control")] 54 | event_emission: true, 55 | phantom: PhantomData, 56 | } 57 | } 58 | } 59 | 60 | impl> UnprotectedStorage for DerefFlaggedStorage { 61 | type AccessMut<'a> = FlaggedAccessMut<'a, >::AccessMut<'a>, C> 62 | where T: 'a; 63 | 64 | unsafe fn clean(&mut self, has: B) 65 | where 66 | B: BitSetLike, 67 | { 68 | // SAFETY: Requirements passed to caller. 69 | unsafe { self.storage.clean(has) }; 70 | } 71 | 72 | unsafe fn get(&self, id: Index) -> &C { 73 | // SAFETY: Requirements passed to caller. 74 | unsafe { self.storage.get(id) } 75 | } 76 | 77 | unsafe fn get_mut(&mut self, id: Index) -> Self::AccessMut<'_> { 78 | let emit = self.emit_event(); 79 | FlaggedAccessMut { 80 | channel: &mut self.channel, 81 | emit, 82 | id, 83 | // SAFETY: Requirements passed to caller. 84 | access: unsafe { self.storage.get_mut(id) }, 85 | phantom: PhantomData, 86 | } 87 | } 88 | 89 | unsafe fn insert(&mut self, id: Index, comp: C) { 90 | if self.emit_event() { 91 | self.channel.single_write(ComponentEvent::Inserted(id)); 92 | } 93 | // SAFETY: Requirements passed to caller. 94 | unsafe { self.storage.insert(id, comp) }; 95 | } 96 | 97 | unsafe fn remove(&mut self, id: Index) -> C { 98 | if self.emit_event() { 99 | self.channel.single_write(ComponentEvent::Removed(id)); 100 | } 101 | // SAFETY: Requirements passed to caller. 102 | unsafe { self.storage.remove(id) } 103 | } 104 | } 105 | 106 | impl Tracked for DerefFlaggedStorage { 107 | fn channel(&self) -> &EventChannel { 108 | &self.channel 109 | } 110 | 111 | fn channel_mut(&mut self) -> &mut EventChannel { 112 | &mut self.channel 113 | } 114 | 115 | #[cfg(feature = "storage-event-control")] 116 | fn set_event_emission(&mut self, emit: bool) { 117 | self.event_emission = emit; 118 | } 119 | 120 | #[cfg(feature = "storage-event-control")] 121 | fn event_emission(&self) -> bool { 122 | self.event_emission 123 | } 124 | } 125 | 126 | /// Wrapper type only emits modificaition events when the component is accessed 127 | /// via mutably dereferencing. Also see [`DerefFlaggedStorage`] documentation. 128 | pub struct FlaggedAccessMut<'a, A, C> { 129 | channel: &'a mut EventChannel, 130 | emit: bool, 131 | id: Index, 132 | access: A, 133 | phantom: PhantomData, 134 | } 135 | 136 | impl<'a, A, C> Deref for FlaggedAccessMut<'a, A, C> 137 | where 138 | A: Deref, 139 | { 140 | type Target = C; 141 | 142 | fn deref(&self) -> &Self::Target { 143 | self.access.deref() 144 | } 145 | } 146 | 147 | impl<'a, A, C> DerefMut for FlaggedAccessMut<'a, A, C> 148 | where 149 | A: AccessMut, 150 | { 151 | fn deref_mut(&mut self) -> &mut Self::Target { 152 | if self.emit { 153 | self.channel.single_write(ComponentEvent::Modified(self.id)); 154 | } 155 | self.access.access_mut() 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/storage/drain.rs: -------------------------------------------------------------------------------- 1 | use hibitset::BitSet; 2 | 3 | #[nougat::gat(Type)] 4 | use crate::join::LendJoin; 5 | use crate::{ 6 | join::{Join, RepeatableLendGet}, 7 | storage::MaskedStorage, 8 | world::{Component, Index}, 9 | }; 10 | 11 | /// A draining storage wrapper which has a `Join` implementation 12 | /// that removes the components. 13 | pub struct Drain<'a, T: Component> { 14 | /// The masked storage 15 | pub data: &'a mut MaskedStorage, 16 | } 17 | 18 | // SAFETY: Calling `get` is always safe! Iterating the mask does not repeat 19 | // indices. 20 | #[nougat::gat] 21 | unsafe impl<'a, T> LendJoin for Drain<'a, T> 22 | where 23 | T: Component, 24 | { 25 | type Mask = BitSet; 26 | type Type<'next> = T; 27 | type Value = &'a mut MaskedStorage; 28 | 29 | unsafe fn open(self) -> (Self::Mask, Self::Value) { 30 | // TODO: Cloning the whole bitset here seems expensive, and it is 31 | // hidden from the user, but there is no obvious way to restructure 32 | // things to avoid this with the way that bitsets are composed together 33 | // for iteration. 34 | let mask = self.data.mask.clone(); 35 | 36 | (mask, self.data) 37 | } 38 | 39 | unsafe fn get<'next>(value: &'next mut Self::Value, id: Index) -> T { 40 | value.remove(id).expect("Tried to access same index twice") 41 | } 42 | } 43 | 44 | // SAFETY: Calling `get` is always safe! 45 | unsafe impl<'a, T> RepeatableLendGet for Drain<'a, T> where T: Component {} 46 | 47 | // SAFETY: Calling `get` is always safe! Iterating the mask does not repeat 48 | // indices. 49 | unsafe impl<'a, T> Join for Drain<'a, T> 50 | where 51 | T: Component, 52 | { 53 | type Mask = BitSet; 54 | type Type = T; 55 | type Value = &'a mut MaskedStorage; 56 | 57 | unsafe fn open(self) -> (Self::Mask, Self::Value) { 58 | // TODO: Cloning the whole bitset here seems expensive, and it is 59 | // hidden from the user, but there is no obvious way to restructure 60 | // things to avoid this with the way that bitsets are composed together 61 | // for iteration. 62 | let mask = self.data.mask.clone(); 63 | 64 | (mask, self.data) 65 | } 66 | 67 | unsafe fn get(value: &mut Self::Value, id: Index) -> T { 68 | value.remove(id).expect("Tried to access same index twice") 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | #[test] 75 | fn basic_drain() { 76 | use crate::{ 77 | join::Join, 78 | storage::DenseVecStorage, 79 | world::{Builder, Component, World, WorldExt}, 80 | }; 81 | 82 | #[derive(Debug, PartialEq)] 83 | struct Comp; 84 | 85 | impl Component for Comp { 86 | type Storage = DenseVecStorage; 87 | } 88 | 89 | let mut world = World::new(); 90 | world.register::(); 91 | 92 | world.create_entity().build(); 93 | let b = world.create_entity().with(Comp).build(); 94 | let c = world.create_entity().with(Comp).build(); 95 | world.create_entity().build(); 96 | let e = world.create_entity().with(Comp).build(); 97 | 98 | let mut comps = world.write_storage::(); 99 | let entities = world.entities(); 100 | 101 | { 102 | let mut iter = (comps.drain(), &entities).join(); 103 | 104 | assert_eq!(iter.next().unwrap(), (Comp, b)); 105 | assert_eq!(iter.next().unwrap(), (Comp, c)); 106 | assert_eq!(iter.next().unwrap(), (Comp, e)); 107 | } 108 | 109 | assert_eq!((&comps).join().count(), 0); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/storage/generic.rs: -------------------------------------------------------------------------------- 1 | use crate::storage::{AccessMut, UnprotectedStorage}; 2 | use crate::{ 3 | storage::{AccessMutReturn, InsertResult, ReadStorage, WriteStorage}, 4 | world::{Component, Entity}, 5 | }; 6 | 7 | pub struct Seal; 8 | 9 | /// Provides generic read access to both `ReadStorage` and `WriteStorage` 10 | pub trait GenericReadStorage { 11 | /// The component type of the storage 12 | type Component: Component; 13 | 14 | /// Get immutable access to an `Entity`s component 15 | fn get(&self, entity: Entity) -> Option<&Self::Component>; 16 | 17 | /// Private function to seal the trait 18 | fn _private() -> Seal; 19 | } 20 | 21 | impl<'a, T> GenericReadStorage for ReadStorage<'a, T> 22 | where 23 | T: Component, 24 | { 25 | type Component = T; 26 | 27 | fn get(&self, entity: Entity) -> Option<&Self::Component> { 28 | ReadStorage::get(self, entity) 29 | } 30 | 31 | fn _private() -> Seal { 32 | Seal 33 | } 34 | } 35 | 36 | impl<'a: 'b, 'b, T> GenericReadStorage for &'b ReadStorage<'a, T> 37 | where 38 | T: Component, 39 | { 40 | type Component = T; 41 | 42 | fn get(&self, entity: Entity) -> Option<&Self::Component> { 43 | ReadStorage::get(*self, entity) 44 | } 45 | 46 | fn _private() -> Seal { 47 | Seal 48 | } 49 | } 50 | 51 | impl<'a, T> GenericReadStorage for WriteStorage<'a, T> 52 | where 53 | T: Component, 54 | { 55 | type Component = T; 56 | 57 | fn get(&self, entity: Entity) -> Option<&Self::Component> { 58 | WriteStorage::get(self, entity) 59 | } 60 | 61 | fn _private() -> Seal { 62 | Seal 63 | } 64 | } 65 | 66 | impl<'a: 'b, 'b, T> GenericReadStorage for &'b WriteStorage<'a, T> 67 | where 68 | T: Component, 69 | { 70 | type Component = T; 71 | 72 | fn get(&self, entity: Entity) -> Option<&Self::Component> { 73 | WriteStorage::get(*self, entity) 74 | } 75 | 76 | fn _private() -> Seal { 77 | Seal 78 | } 79 | } 80 | 81 | /// Provides generic write access to `WriteStorage`, both as a value and a 82 | /// mutable reference. 83 | pub trait GenericWriteStorage { 84 | /// The component type of the storage 85 | type Component: Component; 86 | /// The wrapper through with mutable access of a component is performed. 87 | type AccessMut<'a>: AccessMut 88 | where 89 | Self: 'a; 90 | 91 | /// Get mutable access to an `Entity`s component 92 | fn get_mut(&mut self, entity: Entity) -> Option>; 93 | 94 | /// Get mutable access to an `Entity`s component. If the component does not 95 | /// exist, it is automatically created using `Default::default()`. 96 | /// 97 | /// Returns None if the entity is dead. 98 | fn get_mut_or_default( 99 | &mut self, 100 | entity: Entity, 101 | ) -> Option> 102 | where 103 | Self::Component: Default; 104 | 105 | /// Insert a component for an `Entity` 106 | fn insert(&mut self, entity: Entity, comp: Self::Component) -> InsertResult; 107 | 108 | /// Remove the component for an `Entity` 109 | fn remove(&mut self, entity: Entity); 110 | 111 | /// Private function to seal the trait 112 | fn _private() -> Seal; 113 | } 114 | 115 | impl<'a, T> GenericWriteStorage for WriteStorage<'a, T> 116 | where 117 | T: Component, 118 | { 119 | type AccessMut<'b> = <::Storage as UnprotectedStorage>::AccessMut<'b> 120 | where Self: 'b; 121 | type Component = T; 122 | 123 | fn get_mut(&mut self, entity: Entity) -> Option> { 124 | WriteStorage::get_mut(self, entity) 125 | } 126 | 127 | fn get_mut_or_default(&mut self, entity: Entity) -> Option> 128 | where 129 | Self::Component: Default, 130 | { 131 | if !self.contains(entity) { 132 | self.insert(entity, Default::default()) 133 | .ok() 134 | .and_then(move |_| self.get_mut(entity)) 135 | } else { 136 | self.get_mut(entity) 137 | } 138 | } 139 | 140 | fn insert(&mut self, entity: Entity, comp: Self::Component) -> InsertResult { 141 | WriteStorage::insert(self, entity, comp) 142 | } 143 | 144 | fn remove(&mut self, entity: Entity) { 145 | WriteStorage::remove(self, entity); 146 | } 147 | 148 | fn _private() -> Seal { 149 | Seal 150 | } 151 | } 152 | 153 | impl<'a: 'b, 'b, T> GenericWriteStorage for &'b mut WriteStorage<'a, T> 154 | where 155 | T: Component, 156 | { 157 | type AccessMut<'c> = <::Storage as UnprotectedStorage>::AccessMut<'c> 158 | where Self: 'c; 159 | type Component = T; 160 | 161 | fn get_mut(&mut self, entity: Entity) -> Option> { 162 | WriteStorage::get_mut(*self, entity) 163 | } 164 | 165 | fn get_mut_or_default(&mut self, entity: Entity) -> Option> 166 | where 167 | Self::Component: Default, 168 | { 169 | if !self.contains(entity) { 170 | self.insert(entity, Default::default()) 171 | .ok() 172 | .and_then(move |_| self.get_mut(entity)) 173 | } else { 174 | self.get_mut(entity) 175 | } 176 | } 177 | 178 | fn insert(&mut self, entity: Entity, comp: Self::Component) -> InsertResult { 179 | WriteStorage::insert(*self, entity, comp) 180 | } 181 | 182 | fn remove(&mut self, entity: Entity) { 183 | WriteStorage::remove(*self, entity); 184 | } 185 | 186 | fn _private() -> Seal { 187 | Seal 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/storage/sync_unsafe_cell.rs: -------------------------------------------------------------------------------- 1 | //! Stand in for core::cell::SyncUnsafeCell since that is still unstable. 2 | //! 3 | //! TODO: Remove when core::cell::SyncUnsafeCell is stabilized 4 | 5 | use core::cell::UnsafeCell; 6 | use core::ops::{Deref, DerefMut}; 7 | 8 | #[repr(transparent)] 9 | pub struct SyncUnsafeCell(pub UnsafeCell); 10 | 11 | // SAFETY: Proper synchronization is left to the user of the unsafe `get` call. 12 | // `UnsafeCell` itself doesn't implement `Sync` to prevent accidental mis-use. 13 | unsafe impl Sync for SyncUnsafeCell {} 14 | 15 | impl SyncUnsafeCell { 16 | pub fn new(value: T) -> Self { 17 | Self(UnsafeCell::new(value)) 18 | } 19 | 20 | pub fn as_cell_of_slice(slice: &[Self]) -> &SyncUnsafeCell<[T]> { 21 | // SAFETY: `T` has the same memory layout as `SyncUnsafeCell`. 22 | unsafe { &*(slice as *const [Self] as *const SyncUnsafeCell<[T]>) } 23 | } 24 | 25 | pub fn as_slice_mut(slice: &mut [Self]) -> &mut [T] { 26 | // SAFETY: `T` has the same memory layout as `SyncUnsafeCell` and we 27 | // have a mutable reference which means the `SyncUnsafeCell` can be 28 | // safely removed since we have exclusive access here. 29 | unsafe { &mut *(slice as *mut [Self] as *mut [T]) } 30 | } 31 | } 32 | 33 | impl Deref for SyncUnsafeCell { 34 | type Target = UnsafeCell; 35 | 36 | fn deref(&self) -> &Self::Target { 37 | &self.0 38 | } 39 | } 40 | 41 | impl DerefMut for SyncUnsafeCell { 42 | fn deref_mut(&mut self) -> &mut Self::Target { 43 | &mut self.0 44 | } 45 | } 46 | 47 | impl Default for SyncUnsafeCell { 48 | fn default() -> Self { 49 | Self::new(Default::default()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/storage/track.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use shrev::{EventChannel, ReaderId}; 4 | 5 | use crate::{ 6 | join::Join, 7 | storage::{MaskedStorage, Storage}, 8 | world::{Component, Index}, 9 | }; 10 | 11 | /// `UnprotectedStorage`s that track modifications, insertions, and 12 | /// removals of components. 13 | pub trait Tracked { 14 | /// Event channel tracking modified/inserted/removed components. 15 | fn channel(&self) -> &EventChannel; 16 | /// Mutable event channel tracking modified/inserted/removed components. 17 | fn channel_mut(&mut self) -> &mut EventChannel; 18 | 19 | /// Controls the events signal emission. 20 | /// When this is set to false the events modified/inserted/removed are 21 | /// not emitted. 22 | #[cfg(feature = "storage-event-control")] 23 | fn set_event_emission(&mut self, emit: bool); 24 | 25 | /// Returns the actual state of the event emission. 26 | #[cfg(feature = "storage-event-control")] 27 | fn event_emission(&self) -> bool; 28 | } 29 | 30 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 31 | /// Component storage events received from a `FlaggedStorage` or any storage 32 | /// that implements `Tracked`. 33 | pub enum ComponentEvent { 34 | /// An insertion event, note that a modification event will be triggered if 35 | /// the entity already had a component and had a new one inserted. 36 | Inserted(Index), 37 | /// A modification event, this will be sent any time a component is accessed 38 | /// mutably so be careful with joins over `&mut storages` as it could 39 | /// potentially flag all of them. 40 | Modified(Index), 41 | /// A removal event. 42 | Removed(Index), 43 | } 44 | 45 | impl<'e, T, D> Storage<'e, T, D> 46 | where 47 | T: Component, 48 | T::Storage: Tracked, 49 | D: Deref>, 50 | { 51 | /// Returns the event channel tracking modified components. 52 | pub fn channel(&self) -> &EventChannel { 53 | unsafe { self.open() }.1.channel() 54 | } 55 | 56 | /// Returns the actual state of the event emission. 57 | #[cfg(feature = "storage-event-control")] 58 | pub fn event_emission(&self) -> bool { 59 | unsafe { self.open() }.1.event_emission() 60 | } 61 | } 62 | 63 | impl<'e, T, D> Storage<'e, T, D> 64 | where 65 | T: Component, 66 | T::Storage: Tracked, 67 | D: DerefMut>, 68 | { 69 | /// Returns the event channel for insertions/removals/modifications of this 70 | /// storage's components. 71 | pub fn channel_mut(&mut self) -> &mut EventChannel { 72 | self.data.inner.channel_mut() 73 | } 74 | 75 | /// Starts tracking component events. Note that this reader id should be 76 | /// used every frame, otherwise events will pile up and memory use by 77 | /// the event channel will grow waiting for this reader. 78 | pub fn register_reader(&mut self) -> ReaderId { 79 | self.channel_mut().register_reader() 80 | } 81 | 82 | /// Flags an index with a `ComponentEvent`. 83 | pub fn flag(&mut self, event: ComponentEvent) { 84 | self.channel_mut().single_write(event); 85 | } 86 | 87 | /// Controls the events signal emission. 88 | /// When this is set to false the events modified/inserted/removed are 89 | /// not emitted. 90 | #[cfg(feature = "storage-event-control")] 91 | pub fn set_event_emission(&mut self, emit: bool) { 92 | self.data.inner.set_event_emission(emit); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/world/comp.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::storage::UnprotectedStorage; 4 | 5 | /// Abstract component type. 6 | /// Doesn't have to be Copy or even Clone. 7 | /// 8 | /// ## Storages 9 | /// 10 | /// Components are stored in separated collections for maximum 11 | /// cache efficiency. The `Storage` associated type allows 12 | /// to specify which collection should be used. 13 | /// Depending on how many entities have this component and how 14 | /// often it is accessed, you will want different storages. 15 | /// 16 | /// The most common ones are `VecStorage` (use if almost every entity has that 17 | /// component), `DenseVecStorage` (if you expect many entities to have the 18 | /// component) and `HashMapStorage` (for very rare components). 19 | /// 20 | /// ## Examples 21 | /// 22 | /// ``` 23 | /// use specs::prelude::*; 24 | /// 25 | /// pub struct Position { 26 | /// pub x: f32, 27 | /// pub y: f32, 28 | /// } 29 | /// 30 | /// impl Component for Position { 31 | /// type Storage = VecStorage; 32 | /// } 33 | /// ``` 34 | /// 35 | /// ``` 36 | /// use specs::prelude::*; 37 | /// 38 | /// pub enum Light { 39 | /// // (Variants would have additional data) 40 | /// Directional, 41 | /// SpotLight, 42 | /// } 43 | /// 44 | /// impl Component for Light { 45 | /// type Storage = DenseVecStorage; 46 | /// } 47 | /// ``` 48 | /// 49 | /// ``` 50 | /// use specs::{prelude::*, storage::HashMapStorage}; 51 | /// 52 | /// pub struct Camera { 53 | /// // In an ECS, the camera would not itself have a position; 54 | /// // you would just attach a `Position` component to the same 55 | /// // entity. 56 | /// matrix: [f32; 16], 57 | /// } 58 | /// 59 | /// impl Component for Camera { 60 | /// type Storage = HashMapStorage; 61 | /// } 62 | /// ``` 63 | pub trait Component: Any + Sized { 64 | /// Associated storage type for this component. 65 | #[cfg(feature = "parallel")] 66 | type Storage: UnprotectedStorage + Any + Send + Sync; 67 | 68 | /// Associated storage type for this component. 69 | #[cfg(not(feature = "parallel"))] 70 | type Storage: UnprotectedStorage + Any; 71 | } 72 | -------------------------------------------------------------------------------- /src/world/tests.rs: -------------------------------------------------------------------------------- 1 | use super::{WorldExt, *}; 2 | use crate::{join::Join, storage::VecStorage}; 3 | 4 | struct Pos; 5 | 6 | impl Component for Pos { 7 | type Storage = VecStorage; 8 | } 9 | 10 | struct Vel; 11 | 12 | impl Component for Vel { 13 | type Storage = VecStorage; 14 | } 15 | 16 | #[test] 17 | fn delete_all() { 18 | let mut world = World::new(); 19 | 20 | world.register::(); 21 | world.register::(); 22 | 23 | world.create_entity().build(); 24 | let b = world.create_entity().with(Pos).with(Vel).build(); 25 | world.create_entity().with(Pos).with(Vel).build(); 26 | 27 | assert_eq!(world.entities().join().count(), 3); 28 | 29 | world.delete_all(); 30 | 31 | assert_eq!(world.entities().join().count(), 0); 32 | assert!(world.read_storage::().get(b).is_none()); 33 | } 34 | 35 | #[test] 36 | fn lazy_insertion() { 37 | let mut world = World::new(); 38 | world.register::(); 39 | world.register::(); 40 | 41 | let e1; 42 | let e2; 43 | { 44 | let entities = world.read_resource::(); 45 | let lazy = world.read_resource::(); 46 | 47 | e1 = entities.create(); 48 | e2 = entities.create(); 49 | lazy.insert(e1, Pos); 50 | lazy.insert_all(vec![(e1, Vel), (e2, Vel)]); 51 | } 52 | 53 | world.maintain(); 54 | assert!(world.read_storage::().get(e1).is_some()); 55 | assert!(world.read_storage::().get(e1).is_some()); 56 | assert!(world.read_storage::().get(e2).is_some()); 57 | } 58 | 59 | #[test] 60 | fn lazy_removal() { 61 | let mut world = World::new(); 62 | world.register::(); 63 | 64 | let e = world.create_entity().with(Pos).build(); 65 | { 66 | let lazy = world.read_resource::(); 67 | lazy.remove::(e); 68 | } 69 | 70 | world.maintain(); 71 | assert!(world.read_storage::().get(e).is_none()); 72 | } 73 | 74 | #[test] 75 | fn super_lazy_execution() { 76 | let mut world = World::new(); 77 | world.register::(); 78 | 79 | let e = { 80 | let entity_res = world.read_resource::(); 81 | entity_res.create() 82 | }; 83 | world.read_resource::().exec(move |world| { 84 | world.read_resource::().exec(move |world| { 85 | if let Err(err) = world.write_storage::().insert(e, Pos) { 86 | panic!("Unable to lazily insert component! {:?}", err); 87 | } 88 | }); 89 | assert!(world.read_storage::().get(e).is_none()); 90 | }); 91 | world.maintain(); 92 | assert!(world.read_storage::().get(e).is_some()); 93 | } 94 | 95 | #[test] 96 | fn lazy_execution() { 97 | let mut world = World::new(); 98 | world.register::(); 99 | 100 | let e = { 101 | let entity_res = world.read_resource::(); 102 | entity_res.create() 103 | }; 104 | { 105 | let lazy = world.read_resource::(); 106 | lazy.exec(move |world| { 107 | if let Err(err) = world.write_storage::().insert(e, Pos) { 108 | panic!("Unable to lazily insert component! {:?}", err); 109 | } 110 | }); 111 | } 112 | 113 | world.maintain(); 114 | assert!(world.read_storage::().get(e).is_some()); 115 | } 116 | 117 | #[test] 118 | fn lazy_execution_order() { 119 | let mut world = World::new(); 120 | world.insert(Vec::::new()); 121 | { 122 | let lazy = world.read_resource::(); 123 | lazy.exec(move |world| { 124 | let mut v = world.write_resource::>(); 125 | v.push(1); 126 | }); 127 | lazy.exec(move |world| { 128 | let mut v = world.write_resource::>(); 129 | v.push(2); 130 | }); 131 | } 132 | world.maintain(); 133 | let v = world.read_resource::>(); 134 | assert_eq!(&**v, &[1, 2]); 135 | } 136 | 137 | #[test] 138 | fn delete_twice() { 139 | let mut world = World::new(); 140 | 141 | let e = world.create_entity().build(); 142 | 143 | world.delete_entity(e).unwrap(); 144 | assert!(world.entities().delete(e).is_err()); 145 | } 146 | 147 | #[test] 148 | fn delete_and_lazy() { 149 | let mut world = World::new(); 150 | { 151 | let lazy_update = world.write_resource::(); 152 | lazy_update.exec(|world| { 153 | world.entities().create(); 154 | }) 155 | } 156 | 157 | world.maintain(); 158 | { 159 | let lazy_update = world.write_resource::(); 160 | lazy_update.exec(|world| { 161 | world.entities().create(); 162 | }) 163 | } 164 | 165 | world.delete_all(); 166 | } 167 | -------------------------------------------------------------------------------- /tests/no_parallel.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(feature = "parallel"))] 2 | 3 | use std::rc::Rc; 4 | 5 | use specs::{storage::VecStorage, Builder, Component, World, WorldExt}; 6 | 7 | #[derive(PartialEq)] 8 | struct CompNonSend(Rc); 9 | 10 | impl Component for CompNonSend { 11 | type Storage = VecStorage; 12 | } 13 | 14 | #[test] 15 | fn non_send_component_is_accepted() { 16 | let mut world = World::new(); 17 | world.register::(); 18 | 19 | let entity = world 20 | .create_entity() 21 | .with(CompNonSend(Rc::new(123))) 22 | .build(); 23 | 24 | let comp_non_sends = world.read_storage::(); 25 | let comp_non_send = comp_non_sends 26 | .get(entity) 27 | .expect("Expected component to exist."); 28 | assert_eq!(123, *comp_non_send.0); 29 | } 30 | -------------------------------------------------------------------------------- /tests/saveload.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | #[macro_use] 3 | extern crate serde; 4 | // Specs is renamed here so that the custom derive cannot refer specs directly 5 | #[cfg(feature = "serde")] 6 | extern crate specs as spocs; 7 | 8 | #[cfg(feature = "serde")] 9 | #[macro_use] 10 | extern crate specs_derive; 11 | 12 | #[cfg(feature = "serde")] 13 | mod tests { 14 | #[cfg(feature = "uuid_entity")] 15 | use spocs::saveload::UuidMarker; 16 | use spocs::{ 17 | saveload::{ConvertSaveload, Marker, SimpleMarker}, 18 | Builder, Entity, World, WorldExt, 19 | }; 20 | 21 | #[derive(ConvertSaveload)] 22 | struct OneFieldNamed { 23 | e: Entity, 24 | } 25 | 26 | #[derive(ConvertSaveload)] 27 | struct TwoField { 28 | a: u32, 29 | e: Entity, 30 | } 31 | 32 | // Tests a struct that owns a parent 33 | // that derives Saveload 34 | #[derive(ConvertSaveload)] 35 | struct LevelTwo { 36 | owner: OneFieldNamed, 37 | } 38 | 39 | #[derive(ConvertSaveload)] 40 | struct OneFieldTuple(Entity); 41 | 42 | #[derive(ConvertSaveload)] 43 | struct TwoFieldTuple(Entity, u32); 44 | 45 | #[derive(ConvertSaveload)] 46 | struct LevelTwoTuple(OneFieldNamed); 47 | 48 | #[derive(ConvertSaveload)] 49 | enum AnEnum { 50 | E(Entity), 51 | F { e: Entity }, 52 | Unit, 53 | } 54 | 55 | #[derive(Serialize, Deserialize, Clone)] 56 | struct TupleSerdeType(u32); 57 | 58 | #[derive(Clone)] 59 | #[allow(dead_code)] 60 | struct UnserializableType { 61 | inner: u32, 62 | } 63 | 64 | impl Default for UnserializableType { 65 | fn default() -> Self { 66 | Self { inner: 0 } 67 | } 68 | } 69 | 70 | #[derive(Serialize, Deserialize, Clone)] 71 | #[allow(dead_code)] 72 | struct ComplexSerdeType { 73 | #[serde(skip, default)] 74 | opaque: UnserializableType, 75 | } 76 | 77 | #[derive(ConvertSaveload)] 78 | struct ComplexSerdeMixedType { 79 | #[convert_save_load_skip_convert] 80 | #[convert_save_load_attr(serde(skip, default))] 81 | opaque: UnserializableType, 82 | other: u32, 83 | } 84 | 85 | #[derive(ConvertSaveload)] 86 | pub struct NamedContainsSerdeType { 87 | e: Entity, 88 | a: ComplexSerdeType, 89 | b: ComplexSerdeMixedType, 90 | } 91 | 92 | #[derive(ConvertSaveload)] 93 | struct TupleContainsSerdeType(Entity, ComplexSerdeMixedType); 94 | 95 | #[derive(ConvertSaveload)] 96 | enum NamedEnumContainsSerdeType { 97 | A(Entity), 98 | B { inner_baz: NamedContainsSerdeType }, 99 | } 100 | 101 | #[derive(ConvertSaveload)] 102 | enum UnnamedEnumContainsSerdeType { 103 | A(Entity), 104 | B(NamedContainsSerdeType), 105 | } 106 | 107 | #[derive(ConvertSaveload)] 108 | struct Generic(E); 109 | 110 | trait EntityLike {} 111 | 112 | impl EntityLike for Entity {} 113 | 114 | struct NetworkSync; 115 | 116 | #[test] 117 | fn type_check() { 118 | let mut world = World::new(); 119 | let entity = world.create_entity().build(); 120 | type_check_internal::>(entity); 121 | #[cfg(feature = "uuid_entity")] 122 | type_check_internal::(entity); 123 | } 124 | 125 | fn type_check_internal(entity: Entity) { 126 | black_box::(OneFieldNamed { e: entity }); 127 | black_box::(TwoField { a: 5, e: entity }); 128 | black_box::(LevelTwo { 129 | owner: OneFieldNamed { e: entity }, 130 | }); 131 | black_box::(OneFieldTuple(entity)); 132 | black_box::(TwoFieldTuple(entity, 5)); 133 | black_box::(TupleSerdeType(5)); 134 | black_box::(NamedContainsSerdeType { 135 | e: entity, 136 | a: ComplexSerdeType { 137 | opaque: UnserializableType::default(), 138 | }, 139 | b: ComplexSerdeMixedType { 140 | opaque: UnserializableType::default(), 141 | other: 5, 142 | }, 143 | }); 144 | black_box::(TupleContainsSerdeType( 145 | entity, 146 | ComplexSerdeMixedType { 147 | opaque: UnserializableType::default(), 148 | other: 5, 149 | }, 150 | )); 151 | black_box::(NamedEnumContainsSerdeType::A(entity)); 152 | black_box::(UnnamedEnumContainsSerdeType::A(entity)); 153 | // The derive will work for all variants 154 | // so no need to test anything but unit 155 | black_box::(AnEnum::Unit); 156 | black_box::(Generic(entity)); 157 | } 158 | 159 | fn black_box>(_item: T) {} 160 | } 161 | --------------------------------------------------------------------------------