├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── MIGRATIONS.md ├── README.md ├── bevy_prng ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src │ ├── chacha.rs │ ├── lib.rs │ ├── newtype.rs │ ├── pcg.rs │ ├── wyrand.rs │ └── xoshiro.rs ├── examples └── turn_based_game.rs ├── src ├── commands.rs ├── component.rs ├── global.rs ├── lib.rs ├── observers.rs ├── params.rs ├── plugin.rs ├── prelude.rs ├── seed.rs ├── thread_local_entropy.rs ├── traits.rs └── tutorial.rs ├── tests ├── integration │ ├── bevy_math.rs │ ├── determinism.rs │ ├── extension.rs │ └── reseeding.rs └── suite.rs └── tutorial ├── 00-overview.md ├── 01-choosing-prng.md ├── 02-basic-usage.md ├── 03-components-forking.md ├── 04-seeding.md └── 05-observer-driven-reseeding.md /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - v0.16 9 | 10 | env: 11 | RUSTFLAGS: -D warnings 12 | RUST_BACKTRACE: 1 13 | CARGO_TERM_COLOR: always 14 | CARGO_INCREMENTAL: 0 15 | CARGO_PROFILE_TEST_DEBUG: 0 16 | 17 | jobs: 18 | test: 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | os: [ubuntu-latest, windows-latest, macos-latest] 24 | rust: [nightly, beta, stable] 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Install Rust 28 | # --no-self-update is necessary because the windows environment cannot self-update rustup.exe. 29 | run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} 30 | - uses: Swatinem/rust-cache@v2 31 | # Install Clippy if we are on a nightly run. 32 | - name: Install Clippy 33 | run: rustup component add clippy 34 | if: startsWith(matrix.rust, 'nightly') 35 | - name: Install wasm-pack 36 | uses: taiki-e/install-action@wasm-pack 37 | if: startsWith(matrix.os, 'ubuntu') 38 | - name: Run clippy 39 | run: cargo clippy --locked --workspace --all-features --all-targets -- -Dwarnings 40 | if: startsWith(matrix.rust, 'nightly') 41 | - name: No Default Feature checks 42 | run: cargo check --locked --no-default-features --features rand_xoshiro,rand_pcg --workspace 43 | - name: Test with all features enabled 44 | run: cargo test --locked --all-features 45 | - name: Test Docs 46 | run: cargo doc --locked 47 | - name: Test wasm 48 | env: 49 | RUSTFLAGS: --cfg getrandom_backend="wasm_js" 50 | run: wasm-pack test --headless --chrome --firefox -- --locked --all-features 51 | if: startsWith(matrix.os, 'ubuntu') 52 | - name: Test wasm (no default features) 53 | env: 54 | RUSTFLAGS: --cfg getrandom_backend="wasm_js" 55 | run: wasm-pack test --headless --chrome --firefox -- --locked --no-default-features 56 | if: startsWith(matrix.os, 'ubuntu') 57 | miri: 58 | name: "Miri" 59 | runs-on: ubuntu-latest 60 | timeout-minutes: 15 61 | needs: test 62 | steps: 63 | - uses: actions/checkout@v3 64 | - uses: Swatinem/rust-cache@v2 65 | - name: Install Miri 66 | run: | 67 | rustup toolchain install nightly --component miri 68 | rustup override set nightly 69 | cargo miri setup 70 | - name: Test with Miri 71 | run: cargo miri test --locked 72 | env: 73 | # -Zrandomize-layout makes sure we dont rely on the layout of anything that might change 74 | RUSTFLAGS: -Zrandomize-layout 75 | # https://github.com/rust-lang/miri#miri--z-flags-and-environment-variables 76 | # -Zmiri-disable-isolation is needed because our executor uses `fastrand` which accesses system time. 77 | # -Zmiri-permissive-provenance disables warnings against int2ptr casts (since those are used by once_cell) 78 | # -Zmiri-ignore-leaks is necessary because a bunch of tests don't join all threads before finishing. 79 | MIRIFLAGS: -Zmiri-ignore-leaks -Zmiri-disable-isolation -Zmiri-permissive-provenance 80 | msrv: 81 | runs-on: ubuntu-latest 82 | timeout-minutes: 15 83 | needs: test 84 | steps: 85 | - uses: actions/checkout@v3 86 | - name: get MSRV 87 | run: | 88 | msrv=`cargo metadata --no-deps --format-version 1 | jq --raw-output '.packages[] | select(.name=="wyrand") | .rust_version'` 89 | echo "MSRV=$msrv" >> $GITHUB_ENV 90 | - name: Install Rust 91 | run: rustup update ${{ env.MSRV }} --no-self-update && rustup default ${{ env.MSRV }} 92 | - uses: Swatinem/rust-cache@v2 93 | - name: Run cargo check 94 | id: check 95 | run: cargo check --locked 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | /.cargo 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["bevy_prng"] 3 | resolver = "3" 4 | 5 | [workspace.package] 6 | authors = ["Gonçalo Rica Pais da Silva "] 7 | edition = "2024" 8 | repository = "https://github.com/Bluefinger/bevy_rand" 9 | license = "MIT OR Apache-2.0" 10 | version = "0.11.0" 11 | rust-version = "1.85.0" 12 | 13 | [workspace.dependencies] 14 | bevy_app = { version = "0.16.0", default-features = false } 15 | bevy_ecs = { version = "0.16.0", default-features = false } 16 | bevy_reflect = { version = "0.16.0", default-features = false } 17 | serde = { version = "1.0.218", default-features = false, features = ["derive"] } 18 | rand_core = { version = "0.9.3", features = ["os_rng"] } 19 | rand_core_06 = { version = "0.6.4", default-features = false, package = "rand_core" } 20 | rand_chacha = { version = "0.9.0", default-features = false } 21 | wyrand = "0.3.2" 22 | rand_pcg = "0.9.0" 23 | rand_xoshiro = "0.7.0" 24 | 25 | [package] 26 | name = "bevy_rand" 27 | version = { workspace = true } 28 | edition = { workspace = true } 29 | authors = { workspace = true } 30 | description = "A plugin to integrate rand for ECS optimised RNG for the Bevy game engine." 31 | repository = { workspace = true } 32 | license = { workspace = true } 33 | keywords = ["game", "bevy", "rand", "rng"] 34 | categories = ["game-engines", "algorithms"] 35 | exclude = ["/.*", "Cargo.lock"] 36 | rust-version = { workspace = true } 37 | 38 | [features] 39 | default = ["serialize", "thread_local_entropy", "std", "compat", "bevy_reflect"] 40 | bevy_reflect = [ 41 | "dep:bevy_reflect", 42 | "bevy_app/bevy_reflect", 43 | "bevy_ecs/bevy_reflect", 44 | "bevy_prng/bevy_reflect", 45 | ] 46 | compat = ["bevy_prng/compat", "dep:rand_core_06"] 47 | std = ["bevy_prng/std", "getrandom/std"] 48 | experimental = [] 49 | thread_local_entropy = ["dep:rand_chacha", "std"] 50 | serialize = ["dep:serde", "rand_core/serde", "bevy_prng/serialize"] 51 | rand_chacha = ["bevy_prng/rand_chacha"] 52 | rand_pcg = ["bevy_prng/rand_pcg"] 53 | rand_xoshiro = ["bevy_prng/rand_xoshiro"] 54 | wyrand = ["bevy_prng/wyrand"] 55 | wasm_js = ["getrandom/wasm_js"] 56 | 57 | [dependencies] 58 | bevy_app.workspace = true 59 | bevy_ecs.workspace = true 60 | bevy_reflect = { workspace = true, optional = true } 61 | bevy_prng = { path = "bevy_prng", version = "0.11" } 62 | 63 | # others 64 | getrandom = "0.3.1" 65 | rand_core.workspace = true 66 | rand_core_06 = { workspace = true, optional = true } 67 | rand_chacha = { workspace = true, optional = true } 68 | serde = { workspace = true, optional = true } 69 | 70 | # This cfg cannot be enabled, but it forces Cargo to keep bevy_prng's 71 | # version in lockstep with bevy_rand, so that even minor versions 72 | # cannot be out of step with bevy_rand due to dependencies on traits 73 | # and implementations between the two crates. 74 | [target.'cfg(any())'.dependencies] 75 | bevy_prng = { path = "bevy_prng", version = "=0.11" } 76 | 77 | [dev-dependencies] 78 | bevy_app = { version = "0.16.0", default-features = false, features = [ 79 | "bevy_reflect", 80 | ] } 81 | bevy_ecs = { version = "0.16.0", default-features = false, features = [ 82 | "bevy_reflect", 83 | ] } 84 | bevy_math = { version = "0.16.0", package = "bevy_math" } 85 | bevy_prng = { path = "bevy_prng", version = "0.11", default-features = false, features = [ 86 | "rand_chacha", 87 | "wyrand", 88 | ] } 89 | rand = "0.9" 90 | ron = { version = "0.8.0", features = ["integer128"] } 91 | 92 | [target.'cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))'.dev-dependencies] 93 | wasm-bindgen-test = "0.3" 94 | getrandom = { version = "0.3", features = ["wasm_js"] } 95 | getrandom_02 = { version = "0.2", features = ["js"], package = "getrandom" } 96 | bevy_ecs = { version = "0.16.0", default-features = false, features = [ 97 | "bevy_reflect", 98 | ] } 99 | 100 | [[example]] 101 | name = "turn_based_game" 102 | path = "examples/turn_based_game.rs" 103 | 104 | [package.metadata.docs.rs] 105 | all-features = true 106 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Bluefinger 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Bluefinger 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 | -------------------------------------------------------------------------------- /MIGRATIONS.md: -------------------------------------------------------------------------------- 1 | # Migration Notes 2 | 3 | ## Migrating from v0.2 to v0.3 4 | 5 | As v0.3 is a breaking change to v0.2, the process to migrate over is fairly simple. The rand algorithm crates can no longer be used directly, but they can be swapped wholesale with `bevy_prng` instead. So the following `Cargo.toml` changes: 6 | 7 | ```diff 8 | - rand_chacha = { version = "0.3", features = ["serde1"] } 9 | + bevy_prng = { version = "0.1", features = ["rand_chacha"] } 10 | ``` 11 | 12 | allows then you to swap your import like so, which should then plug straight into existing `bevy_rand` usage seamlessly: 13 | 14 | ```diff 15 | use bevy::prelude::*; 16 | use bevy_rand::prelude::*; 17 | - use rand_chacha::ChaCha8Rng; 18 | + use bevy_prng::ChaCha8Rng; 19 | ``` 20 | 21 | This **will** change the type path and the serialization format for the PRNGs, but currently, moving between different bevy versions has this problem as well as there's currently no means to migrate serialized formats from one version to another yet. The rationale for this change is to enable stable `TypePath` that is being imposed by bevy's reflection system, so that future compiler changes won't break things unexpectedly as `std::any::type_name` has no stability guarantees. Going forward, this should resolve any stability problems `bevy_rand` might have and be able to hook into any migration tool `bevy` might offer for when scene formats change/update. 22 | 23 | ## Migrating from v0.5 to v0.6 24 | 25 | As the `wyrand` dependency has been updated and contains a breaking output change, users of `bevy_rand` making use of the `wyrand` feature will need to update their code in cases where deterministic output from the old version is expected. The new `WyRand` output is considered to provide better entropy than the old version, so it is recommended to adopt the new version. In reality, this is likely to affect tests and serialised output rather than game code. 26 | 27 | ## Migrating from v0.7 to v0.8 28 | 29 | `GlobalRngSeed` has been removed, instead being rolled into `GlobalEntropy`. This will allow better reflection tracking of the global rng source, and will allow for automatic reseeding without any custom system needing to be provided. Use the `reseed` method to reinstantiate the internal RNG source with the new seed, and `get_seed` to return a reference to the initial starting seed for the source. The serialized format of `GlobalEntropy` has changed and previously serialized instances are no longer compatible. 30 | 31 | ## Migrating from v0.8 to v0.9 32 | 33 | The tutorial has been updated to reflect changes to the APIs and intended usages, so please do take a [look at them](https://docs.rs/bevy_rand/latest/bevy_rand/tutorial/index.html). 34 | 35 | `EntropyComponent` has been renamed to `Entropy`, and the trait `SeedableEntropySource` has been renamed to `EntropySource`. The change to `Entropy` also changes the `TypePath` definition, so this will change the serialised format of the component. 36 | 37 | `GlobalEntropy` is no longer a resource, it is a query helper for accessing a `Global` `Entropy` source. It's all entities now, so for "global" and unique sources, they are entities created during plugin initialisation with a `Global` marker component. It is guaranteed to be a single instance per algorithm type, so accessing them is done via `Single` queries. In place of a resource access, there's now helper queries provided in case you need to access the source entity in question for a variety of purposes: 38 | 39 | * `GlobalEntropy` is for accessing the `Entropy` component from `Global`. 40 | * `GlobalSeed` is for accessing the `RngSeed` component from `Global`. This is read-only however, since `RngSeed` is an immutable component. 41 | * `GlobalSource` is for getting the `Entity` of the `Global` source. You likely need this query if you want to reseed the global source, as you'll need to insert a new `RngSeed` component to the entity. 42 | 43 | For most usages of `GlobalEntropy`, updating should be very straightforward: 44 | 45 | ```diff 46 | use bevy_ecs::prelude::*; 47 | use bevy_prng::WyRand; 48 | use bevy_rand::prelude::{GlobalEntropy, ForkableRng}; 49 | 50 | #[derive(Component)] 51 | struct Source; 52 | 53 | - fn setup_source(mut commands: Commands, mut global: ResMut>) { 54 | + fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 55 | commands 56 | .spawn(( 57 | Source, 58 | global.fork_rng(), 59 | )); 60 | } 61 | ``` 62 | 63 | ## Migrating from v0.9 to v0.10 64 | 65 | To begin with, the `experimental` feature no longer does anything, as the observers/commands API is now exposed by default. The feature hasn't been removed, as it may be used for future experimental APIs. There is now a `wasm_js` feature to help configure `getrandom` for WASM, though there's additional steps needed to build for WASM [outlined here](README#usage-within-web-wasm-environments). 66 | 67 | Due to the upgrade to `rand`/`rand_core` v0.9, a lot of crates in the wider `rand` ecosystem have yet to fully transition over to the latest version. As such, there's a new `compat` feature that enables the old `RngCore` trait implementations on the PRNGs in `bevy_prng`, allowing for backwards compatibility. Doing so will pull the older `rand_core` v0.6 as a dependency, but it is enabled without any default features. **NOTE:** This is currently enabled by default, due to `bevy_math` still using the older `rand` versions. 68 | 69 | ```toml 70 | bevy_rand = { version = "0.10", features = ["wyrand", "compat"] } 71 | ``` 72 | 73 | `GlobalSource` and `GlobalSeed` have been removed and now is represented by a `GlobalRngEntity` SystemParam. All uses of `GlobalSource` & `GlobalSeed` can be replaced by `GlobalRngEntity`. 74 | 75 | Various observer events have been removed and replaced with Bevy's relations APIs. `LinkRngSourceToTarget`, `ReseedRng`, `RngChildren` and `RngParent` no longer exist. Instead, for queries, `RngLinks` and `RngSource` are the relations components, and `SeedFromGlobal`, `SeedFromSource`, and `SeedLinked` are the new observer events. For the most part, it is recommended to use the new Commands APIs provided by `RngCommandsExt` & `RngEntityCommandsExt`. 76 | 77 | To enable the full relations API and features, make sure to add the `EntropyRelationsPlugin` to your bevy app. 78 | 79 | ## Migrating from v0.10 to v0.11 80 | 81 | The breaking change here is that when `bevy_rand` has `default-features` set to `false`, it won't bring in `bevy_reflect` support any more. This is so that for more resource constrained `no_std` platforms, reflection support can be opted out in order to conserve memory usage. Reflection support can then be added back in by explicitly specifying `bevy_reflect` as a feature: 82 | 83 | ```toml 84 | bevy_rand = { version = "0.11", default-features = false, features = ["bevy_reflect", "wyrand"] } 85 | ``` 86 | 87 | This change does not affect default/`std` usage of `bevy_rand`, which includes `bevy_reflect` support out of the box. 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bevy Rand 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/bevy_rand.svg)](https://crates.io/crates/bevy_rand) 4 | [![CI](https://github.com/Bluefinger/bevy_rand/actions/workflows/ci.yml/badge.svg)](https://github.com/Bluefinger/bevy_rand/actions/workflows/ci.yml) 5 | [![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)](https://github.com/Bluefinger/bevy_rand) 6 | [![Documentation](https://docs.rs/bevy_rand/badge.svg)](https://docs.rs/bevy_rand) 7 | 8 | ## What is Bevy Rand? 9 | 10 | Bevy Rand is a plugin to provide integration of `rand` ecosystem PRNGs in an ECS friendly way. It provides a set of wrapper component and resource types that allow for safe access to a PRNG for generating random numbers, giving features like reflection, serialization for free. And with these types, it becomes possible to have determinism with the usage of these integrated PRNGs in ways that work with multi-threading and also avoid pitfalls such as unstable query iteration order. 11 | 12 | ## Using Bevy Rand 13 | 14 | > There's now a tutorial, [go to here](https://docs.rs/bevy_rand/latest/bevy_rand/tutorial/index.html) if you want a more comprehensive rundown of how to use `bevy_rand`. 15 | 16 | Usage of Bevy Rand can range from very simple to quite complex use-cases, all depending on whether one cares about deterministic output or not. First, add `bevy_rand`, and either `rand_core` or `rand` to your `Cargo.toml` to bring in both the components and the PRNGs you want to use, along with the various traits needed to use the RNGs. To select a given algorithm type with `bevy_rand`, enable the feature representing the algorithm `rand_*` crate you want to use. This will then give you access to the PRNG structs via the prelude. Alternatively, you can use `bevy_prng` directly to get the newtyped structs with the same feature flags. However, using the algorithm crates like `rand_chacha` directly will not work as these don't implement the necessary traits to support bevy's reflection. 17 | 18 | All supported PRNGs and compatible structs are provided by the `bevy_prng` crate. Simply activate the relevant features in `bevy_rand`/`bevy_prng` to pull in the PRNG algorithm you want to use, and then import them like so: 19 | 20 | #### `bevy_rand` feature activation 21 | ```toml 22 | rand_core = "0.9" 23 | bevy_rand = { version = "0.11", features = ["rand_chacha", "wyrand"] } 24 | ``` 25 | 26 | #### `bevy_prng` feature activation 27 | ```toml 28 | rand_core = "0.9" 29 | bevy_rand = "0.11" 30 | bevy_prng = { version = "0.11", features = ["rand_chacha", "wyrand"] } 31 | ``` 32 | 33 | The summary of what RNG algorithm to choose is: pick `wyrand` for almost all cases as it is faster and more portable than other algorithms. For cases where you need the extra assurance of entropy quality (as in, better and much less predictable 'randomness', etc), then use `rand_chacha`. For more information, [go here](https://docs.rs/bevy_rand/latest/bevy_rand/tutorial/ch01_choosing_prng/index.html). 34 | 35 | DO **NOT** use `bevy_rand` for actual security purposes, as this requires much more careful consideration and properly vetted crates designed for cryptography. A good starting point would be to look at [RustCrypto](https://github.com/RustCrypto) and go from there. 36 | 37 | #### `no_std` support 38 | 39 | `bevy_rand` is `no_std` compatible, but it requires disabling default features. It also assumes that `alloc` is available, just the same as `bevy`. Certain features like `thread_local_entropy` are not available for `no_std` due to requiring `std` specific functionalities like thread locals. 40 | 41 | ```toml 42 | bevy_rand = { version = "0.11", default-features = false, features = ["rand_chacha", "wyrand"] } 43 | ``` 44 | 45 | All PRNG backends should support `no_std` environments. Furthermore, `getrandom` needs to be configured to support the platform, so in the case of a `no_std` environment such as an embedded board or console, you'll need to implement the [custom backend for `getrandom` to compile](https://docs.rs/getrandom/latest/getrandom/#custom-backend). 46 | 47 | #### Usage within Web WASM environments 48 | 49 | From `v0.9` onwards, `bevy_rand` no longer assumes that `bevy` will be run in a web environment when compiled for WASM. To enable that in `v0.11`, just paste the following into your `Cargo.toml` for your binary crate: 50 | 51 | ```toml 52 | [target.'cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))'.dependencies] 53 | bevy_rand = { version = "0.11", features = ["wasm_js"] } 54 | ``` 55 | 56 | This enables the `wasm_js` backend to be made available for `getrandom`, but it doesn't actually build. The next step is to either edit your `.cargo/config.toml` with the below configuration: 57 | 58 | ```toml 59 | # It's recommended to set the flag on a per-target basis: 60 | [target.wasm32-unknown-unknown] 61 | rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] 62 | ``` 63 | 64 | Or pass an environment variable: `RUSTFLAGS='--cfg getrandom_backend="wasm_js"'`. This then enables the `getrandom` WASM backend to get built correctly. 65 | 66 | ### Registering a PRNG for use with Bevy Rand 67 | 68 | Before a PRNG can be used via `GlobalEntropy` or `Entropy`, it must be registered via the plugin. 69 | 70 | ```rust 71 | use bevy_ecs::prelude::*; 72 | use bevy_app::App; 73 | use bevy_prng::WyRand; 74 | use bevy_rand::prelude::EntropyPlugin; 75 | use rand_core::RngCore; 76 | 77 | fn example_main() { 78 | App::new() 79 | .add_plugins(EntropyPlugin::::default()) 80 | .run(); 81 | } 82 | ``` 83 | 84 | ### Basic Usage 85 | 86 | At the simplest case, using `GlobalEntropy` directly for all random number generation, though this does limit how well systems using `GlobalEntropy` can be parallelised. All systems that access `GlobalEntropy` will run serially to each other. 87 | 88 | ```rust 89 | use bevy_prng::WyRand; 90 | use bevy_rand::prelude::GlobalEntropy; 91 | use rand_core::RngCore; 92 | 93 | fn print_random_value(mut rng: GlobalEntropy) { 94 | println!("Random value: {}", rng.next_u32()); 95 | } 96 | ``` 97 | 98 | ### Forking RNGs 99 | 100 | For seeding `Entropy`s from a global source, it is best to make use of forking instead of generating the seed value directly. `GlobalEntropy` can only exist as a singular instance, so when forking normally, it will always fork as `Entropy` instances. 101 | 102 | ```rust 103 | use bevy_ecs::prelude::*; 104 | use bevy_prng::WyRand; 105 | use bevy_rand::prelude::{GlobalEntropy, ForkableRng}; 106 | 107 | #[derive(Component)] 108 | struct Source; 109 | 110 | fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 111 | commands 112 | .spawn(( 113 | Source, 114 | global.fork_rng(), 115 | )); 116 | } 117 | ``` 118 | 119 | `Entropy`s can be seeded/forked from other `Entropy`s as well. 120 | 121 | ```rust 122 | use bevy_ecs::prelude::*; 123 | use bevy_prng::WyRand; 124 | use bevy_rand::prelude::{Entropy, ForkableRng}; 125 | 126 | #[derive(Component)] 127 | struct Npc; 128 | 129 | #[derive(Component)] 130 | struct Source; 131 | 132 | fn setup_npc_from_source( 133 | mut commands: Commands, 134 | mut q_source: Single<&mut Entropy, (With, Without)>, 135 | ) { 136 | for _ in 0..2 { 137 | commands 138 | .spawn(( 139 | Npc, 140 | q_source.fork_rng() 141 | )); 142 | } 143 | } 144 | ``` 145 | 146 | ## Features 147 | 148 | - **`bevy_reflect`** - Enables reflection support for all `bevy_rand` types. Enabled by default. 149 | - **`std`** - Enables support for `std` environment, allows enabling `std` specific optimisations for `rand_chacha` and more. Enabled by default. 150 | - **`thread_local_entropy`** - Enables `ThreadLocalEntropy`, overriding `SeedableRng::from_entropy` implementations to make use of thread local entropy sources for faster PRNG initialisation. Requires `std` environments so it enables the `std` feature. Enabled by default. 151 | - **`serialize`** - Enables `Serialize` and `Deserialize` derives. Enabled by default. 152 | - **`rand_chacha`** - This enables the exporting of newtyped `ChaCha*Rng` structs, for those that want/need to use a CSPRNG level source. 153 | - **`rand_pcg`** - This enables the exporting of newtyped `Pcg*` structs from `rand_pcg`. 154 | - **`rand_xoshiro`** - This enables the exporting of newtyped `Xoshiro*` structs from `rand_xoshiro`. It also exports a remote-reflected version of `Seed512` so to allow setting up `Xoshiro512StarStar` and so forth. 155 | - **`wyrand`** - This enables the exporting of newtyped `WyRand` from `wyrand`, the same algorithm in use within `fastrand`/`turborand`. 156 | - **`experimental`** - This enables any unstable/experimental features for `bevy_rand`. Currently, this does nothing at the moment. 157 | - **`wasm_js`** - This enables the `getrandom` WASM backend, though doesn't make `getrandom` use it. That requires extra steps outlined [here](#usage-within-web-wasm-environments). 158 | - **`compat`** - This enables the old v0.6 `RngCore` trait implementation on the RNGs, providing additional compatibility with other crates that haven't yet upgraded to the latest `rand_core`/`rand` versions. **Currently enabled by default in order to support `bevy_math`, which is still using `rand` v0.8**. 159 | 160 | ## Supported Versions & MSRV 161 | 162 | `bevy_rand` uses the same MSRV policy as `bevy`. 163 | 164 | | `bevy` | `bevy_rand` | 165 | | ------ | ------------- | 166 | | v0.16 | v0.10 - v0.11 | 167 | | v0.15 | v0.8 - v0.9 | 168 | | v0.14 | v0.7 | 169 | | v0.13 | v0.5 - v0.6 | 170 | | v0.12 | v0.4 | 171 | | v0.11 | v0.2 - v0.3 | 172 | | v0.10 | v0.1 | 173 | 174 | The versions of `rand_core`/`rand` that `bevy_rand` is compatible with is as follows: 175 | 176 | | `bevy_rand` | `rand_core` | `rand` | `getrandom` | `compat` feature | 177 | | -------------- | ----------- | ------ | ----------- | ------------------------------ | 178 | | v0.10 -> v0.11 | v0.9 | v0.9 | v0.3 | ✅ (supports `rand_core` v0.6) | 179 | | v0.1 -> v0.9 | v0.6 | v0.8 | v0.2 | ❌ | 180 | 181 | ## Migrations 182 | 183 | Notes on migrating between versions can be found [here](MIGRATIONS.md). 184 | 185 | ## License 186 | 187 | Licensed under either of 188 | 189 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) 190 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 191 | 192 | at your option. 193 | -------------------------------------------------------------------------------- /bevy_prng/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_prng" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | authors = { workspace = true } 6 | description = "A crate providing newtyped RNGs for integration into Bevy." 7 | repository = { workspace = true } 8 | license = { workspace = true } 9 | keywords = ["game", "bevy", "rand", "rng"] 10 | categories = ["game-engines", "algorithms"] 11 | exclude = ["/.*"] 12 | rust-version = { workspace = true } 13 | 14 | [features] 15 | default = [] 16 | std = ["rand_chacha?/std"] 17 | bevy_reflect = ["dep:bevy_reflect"] 18 | serialize = [ 19 | "dep:serde", 20 | "rand_core/serde", 21 | "rand_chacha?/serde", 22 | "rand_pcg?/serde", 23 | "rand_xoshiro?/serde", 24 | "wyrand?/serde1", 25 | ] 26 | rand_chacha = ["dep:rand_chacha"] 27 | wyrand = ["dep:wyrand"] 28 | rand_pcg = ["dep:rand_pcg"] 29 | rand_xoshiro = ["dep:rand_xoshiro"] 30 | compat = ["dep:rand_core_06"] 31 | 32 | [dependencies] 33 | bevy_reflect = { workspace = true, optional = true } 34 | rand_core.workspace = true 35 | rand_core_06 = { workspace = true, optional = true } 36 | serde = { workspace = true, optional = true } 37 | rand_chacha = { workspace = true, optional = true } 38 | wyrand = { workspace = true, optional = true } 39 | rand_pcg = { workspace = true, optional = true } 40 | rand_xoshiro = { workspace = true, optional = true } 41 | 42 | [package.metadata.docs.rs] 43 | all-features = true 44 | -------------------------------------------------------------------------------- /bevy_prng/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Bluefinger 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /bevy_prng/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Bluefinger 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 | -------------------------------------------------------------------------------- /bevy_prng/README.md: -------------------------------------------------------------------------------- 1 | # Bevy PRNG 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/bevy_prng.svg)](https://crates.io/crates/bevy_prng) 4 | [![CI](https://github.com/Bluefinger/bevy_rand/actions/workflows/ci.yml/badge.svg)](https://github.com/Bluefinger/bevy_rand/actions/workflows/ci.yml) 5 | [![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)](https://github.com/Bluefinger/bevy_rand) 6 | [![Documentation](https://docs.rs/bevy_prng/badge.svg)](https://docs.rs/bevy_prng) 7 | 8 | ## What is Bevy PRNG? 9 | 10 | `bevy_prng` is a crate that provides newtyped versions of various `rand_*` PRNG algorithm crates to make them suitable for integration within `bevy` for reflection purposes. It enables these types to have stable `TypePath`s and otherwise implement various required traits. This crate can be used as standalone to provide access to various PRNG algorithms of one's choice, to then use to write components/resources for one's game in `bevy`, but primarily, it's purpose to support and be a counterpart to `bevy_rand` (which provides the generic wrapper component/resource that `bevy_prng` types can plug in to). 11 | 12 | This crate is `no_std` compatible. 13 | 14 | ## Using Bevy PRNG 15 | 16 | By default, `bevy_prng` won't export anything _unless_ the feature/algorithm you require is explicitly defined. In order to gain access to a newtyped PRNG struct, you'll have activate one of the following features: 17 | 18 | - **`bevy_reflect`** - Enables reflection support for all `bevy_prng` types. 19 | - **`std`** - This enables some `std` specific functionality in some PRNGs, particularly in `rand_chacha`. Only for `std` environments. 20 | - **`rand_chacha`** - This enables the exporting of newtyped `ChaCha*Rng` structs, for those that want/need to use a CSPRNG level source. 21 | - **`rand_pcg`** - This enables the exporting of newtyped `Pcg*` structs from `rand_pcg`. 22 | - **`rand_xoshiro`** - This enables the exporting of newtyped `Xoshiro*` structs from `rand_xoshiro`. It also exports a remote-reflected version of `Seed512` so to allow setting up `Xoshiro512StarStar` and so forth. 23 | - **`wyrand`** - This enables the exporting of newtyped `WyRand` from `wyrand`, the same algorithm in use within `fastrand`/`turborand`. 24 | - **`compat`** - This enables the old `RngCore` trait implementations on the RNGs, providing additional compatibility with other crates that haven't yet upgraded to the latest `rand_core`/`rand` versions. 25 | 26 | In addition to these feature flags to enable various supported algorithms, there's also **`serialize`** flag to provide `serde` support for `Serialize`/`Deserialize`, which is enabled by default. 27 | 28 | All types are provided at the top-level of the module: 29 | 30 | ```rust ignore 31 | use bevy_prng::*; 32 | ``` 33 | 34 | ## Supported PRNG Algorithms/Crates 35 | 36 | All the below crates implement the necessary traits to be compatible with `bevy_prng`. Additional PRNG crates can be added via PR's to this crate/repo, provided the PRNGs implement `Debug`, `Clone`, `PartialEq` and have optional `Serialize`/`Deserialize` `serde` traits implemented and put behind appropriate feature flags. 37 | 38 | ### Cryptographically Secure PRNGs 39 | 40 | - [rand_chacha](https://crates.io/crates/rand_chacha) 41 | 42 | ### Non-Cryptographically Secure PRNGS 43 | 44 | - [wyrand](https://crates.io/crates/wyrand) 45 | - [rand_xoshiro](https://crates.io/crates/rand_xoshiro) 46 | - [rand_pcg](https://crates.io/crates/rand_pcg) 47 | 48 | ## Supported Versions & MSRV 49 | 50 | `bevy_prng` uses the same MSRV as `bevy`. 51 | 52 | | `bevy` | `bevy_prng` | 53 | | ------ | ------------- | 54 | | v0.16 | v0.10 - v0.11 | 55 | | v0.15 | v0.8 - v0.9 | 56 | | v0.14 | v0.7 - v0.8 | 57 | | v0.13 | v0.5 - v0.6 | 58 | | v0.12 | v0.2 | 59 | | v0.11 | v0.1 | 60 | 61 | The versions of `rand_core`/`rand` that `bevy_prng` is compatible with is as follows: 62 | 63 | | `bevy_prng` | `rand_core` | `rand` | `getrandom` | `compat` feature | 64 | | -------------- | ----------- | ------ | ----------- | ------------------------------ | 65 | | v0.10 -> v0.11 | v0.9 | v0.9 | v0.3 | ✅ (supports `rand_core` v0.6) | 66 | | v0.1 -> v0.9 | v0.6 | v0.8 | v0.2 | ❌ | 67 | 68 | ## License 69 | 70 | Licensed under either of 71 | 72 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) 73 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 74 | 75 | at your option. 76 | -------------------------------------------------------------------------------- /bevy_prng/src/chacha.rs: -------------------------------------------------------------------------------- 1 | use crate::newtype::newtype_prng; 2 | 3 | #[cfg(feature = "bevy_reflect")] 4 | use bevy_reflect::{Reflect, ReflectFromReflect}; 5 | 6 | #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] 7 | use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; 8 | 9 | newtype_prng!( 10 | ChaCha8Rng, 11 | ::rand_chacha::ChaCha8Rng, 12 | "A newtyped [`rand_chacha::ChaCha8Rng`] RNG", 13 | "rand_chacha" 14 | ); 15 | 16 | newtype_prng!( 17 | ChaCha12Rng, 18 | ::rand_chacha::ChaCha12Rng, 19 | "A newtyped [`rand_chacha::ChaCha12Rng`] RNG", 20 | "rand_chacha" 21 | ); 22 | 23 | newtype_prng!( 24 | ChaCha20Rng, 25 | ::rand_chacha::ChaCha20Rng, 26 | "A newtyped [`rand_chacha::ChaCha20Rng`] RNG", 27 | "rand_chacha" 28 | ); 29 | -------------------------------------------------------------------------------- /bevy_prng/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![deny(missing_docs)] 3 | #![cfg_attr(docsrs, feature(doc_cfg))] 4 | #![cfg_attr(docsrs, allow(unused_attributes))] 5 | #![no_std] 6 | 7 | #[cfg(feature = "rand_chacha")] 8 | mod chacha; 9 | #[cfg(any( 10 | feature = "wyrand", 11 | feature = "rand_chacha", 12 | feature = "rand_pcg", 13 | feature = "rand_xoshiro" 14 | ))] 15 | mod newtype; 16 | #[cfg(feature = "rand_pcg")] 17 | mod pcg; 18 | #[cfg(feature = "wyrand")] 19 | mod wyrand; 20 | #[cfg(feature = "rand_xoshiro")] 21 | mod xoshiro; 22 | 23 | use core::fmt::Debug; 24 | 25 | #[cfg(feature = "bevy_reflect")] 26 | use bevy_reflect::{FromReflect, Reflectable, Typed}; 27 | use rand_core::{RngCore, SeedableRng}; 28 | #[cfg(feature = "serialize")] 29 | use serde::{Deserialize, Serialize}; 30 | 31 | #[cfg(feature = "rand_chacha")] 32 | pub use chacha::*; 33 | #[cfg(feature = "rand_pcg")] 34 | pub use pcg::*; 35 | #[cfg(feature = "wyrand")] 36 | pub use wyrand::WyRand; 37 | #[cfg(feature = "rand_xoshiro")] 38 | pub use xoshiro::*; 39 | 40 | /// Trait for handling `SeedableRng` requirements, imposing constraints 41 | /// depending on whether reflection support is enabled or not 42 | #[cfg(feature = "bevy_reflect")] 43 | pub trait TypedSeed: SeedableRng {} 44 | 45 | #[cfg(feature = "bevy_reflect")] 46 | impl> TypedSeed for T {} 47 | 48 | /// Trait for handling `SeedableRng` requirements, imposing constraints 49 | /// depending on whether reflection support is enabled or not 50 | #[cfg(not(feature = "bevy_reflect"))] 51 | pub trait TypedSeed: SeedableRng {} 52 | 53 | #[cfg(not(feature = "bevy_reflect"))] 54 | impl> TypedSeed for T {} 55 | 56 | /// Trait for handling contraints for valid implementations of [`EntropySource`] 57 | /// depending on whether reflection support is enabled or not 58 | #[cfg(feature = "bevy_reflect")] 59 | pub trait RngReflectable: FromReflect + Reflectable {} 60 | 61 | #[cfg(feature = "bevy_reflect")] 62 | impl RngReflectable for T {} 63 | 64 | /// Trait for handling contraints for valid implementations of [`EntropySource`] 65 | /// depending on whether reflection support is enabled or not 66 | #[cfg(not(feature = "bevy_reflect"))] 67 | pub trait RngReflectable: 'static {} 68 | 69 | #[cfg(not(feature = "bevy_reflect"))] 70 | impl RngReflectable for T {} 71 | 72 | /// A marker trait to define the required trait bounds for a seedable PRNG to 73 | /// integrate into `Entropy` or `GlobalEntropy`. This is a sealed trait. 74 | #[cfg(feature = "serialize")] 75 | pub trait EntropySource: 76 | RngCore 77 | + RngReflectable 78 | + TypedSeed 79 | + Clone 80 | + Debug 81 | + PartialEq 82 | + Sync 83 | + Send 84 | + Serialize 85 | + for<'a> Deserialize<'a> 86 | + private::SealedSeedable 87 | { 88 | } 89 | 90 | /// Marker trait for a suitable seed for [`EntropySource`]. This is an auto trait which will 91 | /// apply to all suitable types that meet the trait criteria. 92 | #[cfg(feature = "serialize")] 93 | pub trait EntropySeed: 94 | Debug 95 | + Default 96 | + PartialEq 97 | + AsMut<[u8]> 98 | + Clone 99 | + Sync 100 | + Send 101 | + RngReflectable 102 | + Serialize 103 | + for<'a> Deserialize<'a> 104 | { 105 | } 106 | 107 | #[cfg(feature = "serialize")] 108 | impl< 109 | T: Debug 110 | + Default 111 | + PartialEq 112 | + AsMut<[u8]> 113 | + Clone 114 | + Sync 115 | + Send 116 | + RngReflectable 117 | + Serialize 118 | + for<'a> Deserialize<'a>, 119 | > EntropySeed for T 120 | { 121 | } 122 | 123 | /// A marker trait to define the required trait bounds for a seedable PRNG to 124 | /// integrate into `Entropy` or `GlobalEntropy`. This is a sealed trait. 125 | #[cfg(not(feature = "serialize"))] 126 | pub trait EntropySource: 127 | RngCore 128 | + TypedSeed 129 | + Clone 130 | + Debug 131 | + PartialEq 132 | + RngReflectable 133 | + Sync 134 | + Send 135 | + private::SealedSeedable 136 | { 137 | } 138 | 139 | #[cfg(not(feature = "serialize"))] 140 | /// Marker trait for a suitable seed for [`EntropySource`]. This is an auto trait which will 141 | /// apply to all suitable types that meet the trait criteria. 142 | pub trait EntropySeed: 143 | Debug + Default + PartialEq + AsMut<[u8]> + Clone + Sync + Send + RngReflectable 144 | { 145 | } 146 | 147 | #[cfg(not(feature = "serialize"))] 148 | impl + Clone + Sync + Send + RngReflectable> 149 | EntropySeed for T 150 | { 151 | } 152 | 153 | mod private { 154 | pub trait SealedSeedable {} 155 | 156 | impl SealedSeedable for T {} 157 | } 158 | -------------------------------------------------------------------------------- /bevy_prng/src/newtype.rs: -------------------------------------------------------------------------------- 1 | macro_rules! newtype_prng { 2 | ($newtype:tt, $rng:ty, $doc:tt, $feature:tt) => { 3 | #[doc = $doc] 4 | #[derive(Debug, Clone, PartialEq)] 5 | #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] 6 | #[cfg_attr(feature = "bevy_reflect", reflect(opaque))] 7 | #[cfg_attr( 8 | feature = "serialize", 9 | derive(::serde::Serialize, ::serde::Deserialize) 10 | )] 11 | #[cfg_attr( 12 | all(feature = "serialize", feature = "bevy_reflect"), 13 | reflect(opaque, Debug, PartialEq, FromReflect, Serialize, Deserialize) 14 | )] 15 | #[cfg_attr( 16 | all(not(feature = "serialize"), feature = "bevy_reflect"), 17 | reflect(opaque, Debug, PartialEq, FromReflect) 18 | )] 19 | #[cfg_attr(docsrs, doc(cfg(feature = $feature)))] 20 | #[cfg_attr(feature = "bevy_reflect", type_path = "bevy_prng")] 21 | #[repr(transparent)] 22 | pub struct $newtype($rng); 23 | 24 | impl $newtype { 25 | /// Create a new instance. 26 | #[inline(always)] 27 | #[must_use] 28 | pub fn new(rng: $rng) -> Self { 29 | Self(rng) 30 | } 31 | } 32 | 33 | impl ::rand_core::RngCore for $newtype { 34 | #[inline(always)] 35 | fn next_u32(&mut self) -> u32 { 36 | ::rand_core::RngCore::next_u32(&mut self.0) 37 | } 38 | 39 | #[inline(always)] 40 | fn next_u64(&mut self) -> u64 { 41 | ::rand_core::RngCore::next_u64(&mut self.0) 42 | } 43 | 44 | #[inline(always)] 45 | fn fill_bytes(&mut self, dest: &mut [u8]) { 46 | ::rand_core::RngCore::fill_bytes(&mut self.0, dest) 47 | } 48 | } 49 | 50 | #[cfg(feature = "compat")] 51 | impl ::rand_core_06::RngCore for $newtype { 52 | #[inline(always)] 53 | fn next_u32(&mut self) -> u32 { 54 | ::rand_core::RngCore::next_u32(&mut self.0) 55 | } 56 | 57 | #[inline(always)] 58 | fn next_u64(&mut self) -> u64 { 59 | ::rand_core::RngCore::next_u64(&mut self.0) 60 | } 61 | 62 | #[inline(always)] 63 | fn fill_bytes(&mut self, dest: &mut [u8]) { 64 | ::rand_core::RngCore::fill_bytes(&mut self.0, dest) 65 | } 66 | 67 | #[inline(always)] 68 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), ::rand_core_06::Error> { 69 | ::rand_core::RngCore::fill_bytes(&mut self.0, dest); 70 | Ok(()) 71 | } 72 | } 73 | 74 | impl ::rand_core::SeedableRng for $newtype { 75 | type Seed = <$rng as ::rand_core::SeedableRng>::Seed; 76 | 77 | #[inline] 78 | fn from_seed(seed: Self::Seed) -> Self { 79 | Self::new(<$rng>::from_seed(seed)) 80 | } 81 | 82 | #[inline] 83 | fn from_rng(source: &mut impl ::rand_core::RngCore) -> Self { 84 | Self::new(<$rng>::from_rng(source)) 85 | } 86 | 87 | #[inline] 88 | fn try_from_rng(source: &mut T) -> Result { 89 | Ok(Self::new(<$rng>::try_from_rng(source)?)) 90 | } 91 | } 92 | 93 | impl From<$rng> for $newtype { 94 | #[inline] 95 | fn from(value: $rng) -> Self { 96 | Self::new(value) 97 | } 98 | } 99 | 100 | impl crate::EntropySource for $newtype {} 101 | }; 102 | } 103 | 104 | #[cfg(all(feature = "rand_xoshiro", feature = "bevy_reflect"))] 105 | macro_rules! newtype_prng_remote { 106 | ($newtype:tt, $rng:ty, $seed:ty, $doc:tt, $feature:tt) => { 107 | #[doc = $doc] 108 | #[derive(Debug, Clone, PartialEq)] 109 | #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] 110 | #[cfg_attr(feature = "bevy_reflect", reflect(opaque))] 111 | #[cfg_attr( 112 | feature = "serialize", 113 | derive(::serde::Serialize, ::serde::Deserialize) 114 | )] 115 | #[cfg_attr( 116 | all(feature = "serialize", feature = "bevy_reflect"), 117 | reflect(opaque, Debug, PartialEq, FromReflect, Serialize, Deserialize) 118 | )] 119 | #[cfg_attr( 120 | all(not(feature = "serialize"), feature = "bevy_reflect"), 121 | reflect(opaque, Debug, PartialEq, FromReflect) 122 | )] 123 | #[cfg_attr(docsrs, doc(cfg(feature = $feature)))] 124 | #[cfg_attr(feature = "bevy_reflect", type_path = "bevy_prng")] 125 | #[repr(transparent)] 126 | pub struct $newtype($rng); 127 | 128 | impl $newtype { 129 | /// Create a new instance. 130 | #[inline(always)] 131 | #[must_use] 132 | pub fn new(rng: $rng) -> Self { 133 | Self(rng) 134 | } 135 | } 136 | 137 | impl ::rand_core::RngCore for $newtype { 138 | #[inline(always)] 139 | fn next_u32(&mut self) -> u32 { 140 | ::rand_core::RngCore::next_u32(&mut self.0) 141 | } 142 | 143 | #[inline(always)] 144 | fn next_u64(&mut self) -> u64 { 145 | ::rand_core::RngCore::next_u64(&mut self.0) 146 | } 147 | 148 | #[inline(always)] 149 | fn fill_bytes(&mut self, dest: &mut [u8]) { 150 | ::rand_core::RngCore::fill_bytes(&mut self.0, dest) 151 | } 152 | } 153 | 154 | #[cfg(feature = "compat")] 155 | impl ::rand_core_06::RngCore for $newtype { 156 | #[inline(always)] 157 | fn next_u32(&mut self) -> u32 { 158 | ::rand_core::RngCore::next_u32(&mut self.0) 159 | } 160 | 161 | #[inline(always)] 162 | fn next_u64(&mut self) -> u64 { 163 | ::rand_core::RngCore::next_u64(&mut self.0) 164 | } 165 | 166 | #[inline(always)] 167 | fn fill_bytes(&mut self, dest: &mut [u8]) { 168 | ::rand_core::RngCore::fill_bytes(&mut self.0, dest) 169 | } 170 | 171 | #[inline(always)] 172 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), ::rand_core_06::Error> { 173 | ::rand_core::RngCore::fill_bytes(&mut self.0, dest); 174 | Ok(()) 175 | } 176 | } 177 | 178 | impl ::rand_core::SeedableRng for $newtype { 179 | type Seed = $seed; 180 | 181 | #[inline] 182 | fn from_seed(seed: Self::Seed) -> Self { 183 | Self::new(<$rng>::from_seed(seed.0)) 184 | } 185 | 186 | #[inline] 187 | fn from_rng(source: &mut impl ::rand_core::RngCore) -> Self { 188 | Self::new(<$rng>::from_rng(source)) 189 | } 190 | 191 | #[inline] 192 | fn try_from_rng(source: &mut T) -> Result { 193 | Ok(Self::new(<$rng>::try_from_rng(source)?)) 194 | } 195 | } 196 | 197 | impl From<$rng> for $newtype { 198 | #[inline] 199 | fn from(value: $rng) -> Self { 200 | Self::new(value) 201 | } 202 | } 203 | 204 | impl crate::EntropySource for $newtype {} 205 | }; 206 | } 207 | 208 | pub(crate) use newtype_prng; 209 | #[cfg(all(feature = "rand_xoshiro", feature = "bevy_reflect"))] 210 | pub(crate) use newtype_prng_remote; 211 | -------------------------------------------------------------------------------- /bevy_prng/src/pcg.rs: -------------------------------------------------------------------------------- 1 | use crate::newtype::newtype_prng; 2 | 3 | #[cfg(feature = "bevy_reflect")] 4 | use bevy_reflect::{Reflect, ReflectFromReflect}; 5 | 6 | #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] 7 | use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; 8 | 9 | newtype_prng!( 10 | Pcg32, 11 | ::rand_pcg::Pcg32, 12 | "A newtyped [`rand_pcg::Pcg32`] RNG", 13 | "rand_pcg" 14 | ); 15 | 16 | newtype_prng!( 17 | Pcg64, 18 | ::rand_pcg::Pcg64, 19 | "A newtyped [`rand_pcg::Pcg64`] RNG", 20 | "rand_pcg" 21 | ); 22 | 23 | newtype_prng!( 24 | Pcg64Mcg, 25 | ::rand_pcg::Pcg64Mcg, 26 | "A newtyped [`rand_pcg::Pcg64Mcg`] RNG", 27 | "rand_pcg" 28 | ); 29 | -------------------------------------------------------------------------------- /bevy_prng/src/wyrand.rs: -------------------------------------------------------------------------------- 1 | use crate::newtype::newtype_prng; 2 | 3 | #[cfg(feature = "bevy_reflect")] 4 | use bevy_reflect::{Reflect, ReflectFromReflect}; 5 | 6 | #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] 7 | use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; 8 | 9 | newtype_prng!( 10 | WyRand, 11 | ::wyrand::WyRand, 12 | "A newtyped [`wyrand::WyRand`] RNG", 13 | "wyrand" 14 | ); 15 | -------------------------------------------------------------------------------- /bevy_prng/src/xoshiro.rs: -------------------------------------------------------------------------------- 1 | use crate::newtype::newtype_prng; 2 | 3 | #[cfg(feature = "bevy_reflect")] 4 | use crate::newtype::newtype_prng_remote; 5 | 6 | #[cfg(feature = "bevy_reflect")] 7 | use bevy_reflect::{Reflect, ReflectFromReflect, reflect_remote, std_traits::ReflectDefault}; 8 | 9 | #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] 10 | use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; 11 | 12 | #[cfg(feature = "bevy_reflect")] 13 | /// Remote reflected version of [`rand_xoshiro::Seed512`], needed to support 14 | /// proper reflection for the 512 bit variants of the Xoshiro PRNG. 15 | #[cfg_attr(feature = "bevy_reflect", reflect_remote(::rand_xoshiro::Seed512))] 16 | #[derive(Debug, Default, Clone)] 17 | #[reflect(Debug, Default)] 18 | pub struct Seed512(pub [u8; 64]); 19 | 20 | #[cfg(feature = "bevy_reflect")] 21 | impl AsRef<[u8]> for Seed512 { 22 | fn as_ref(&self) -> &[u8] { 23 | self.0.as_ref() 24 | } 25 | } 26 | 27 | #[cfg(feature = "bevy_reflect")] 28 | impl AsMut<[u8]> for Seed512 { 29 | fn as_mut(&mut self) -> &mut [u8] { 30 | self.0.as_mut() 31 | } 32 | } 33 | 34 | #[cfg(feature = "bevy_reflect")] 35 | newtype_prng_remote!( 36 | Xoshiro512StarStar, 37 | ::rand_xoshiro::Xoshiro512StarStar, 38 | Seed512, 39 | "A newtyped [`rand_xoshiro::Xoshiro512StarStar`] RNG", 40 | "rand_xoshiro" 41 | ); 42 | 43 | #[cfg(not(feature = "bevy_reflect"))] 44 | newtype_prng!( 45 | Xoshiro512StarStar, 46 | ::rand_xoshiro::Xoshiro512StarStar, 47 | "A newtyped [`rand_xoshiro::Xoshiro512StarStar`] RNG", 48 | "rand_xoshiro" 49 | ); 50 | 51 | #[cfg(feature = "bevy_reflect")] 52 | newtype_prng_remote!( 53 | Xoshiro512PlusPlus, 54 | ::rand_xoshiro::Xoshiro512PlusPlus, 55 | Seed512, 56 | "A newtyped [`rand_xoshiro::Xoshiro512PlusPlus`] RNG", 57 | "rand_xoshiro" 58 | ); 59 | 60 | #[cfg(not(feature = "bevy_reflect"))] 61 | newtype_prng!( 62 | Xoshiro512PlusPlus, 63 | ::rand_xoshiro::Xoshiro512PlusPlus, 64 | "A newtyped [`rand_xoshiro::Xoshiro512PlusPlus`] RNG", 65 | "rand_xoshiro" 66 | ); 67 | 68 | #[cfg(feature = "bevy_reflect")] 69 | newtype_prng_remote!( 70 | Xoshiro512Plus, 71 | ::rand_xoshiro::Xoshiro512Plus, 72 | Seed512, 73 | "A newtyped [`rand_xoshiro::Xoshiro512Plus`] RNG", 74 | "rand_xoshiro" 75 | ); 76 | 77 | #[cfg(not(feature = "bevy_reflect"))] 78 | newtype_prng!( 79 | Xoshiro512Plus, 80 | ::rand_xoshiro::Xoshiro512Plus, 81 | "A newtyped [`rand_xoshiro::Xoshiro512Plus`] RNG", 82 | "rand_xoshiro" 83 | ); 84 | 85 | newtype_prng!( 86 | Xoshiro256StarStar, 87 | ::rand_xoshiro::Xoshiro256StarStar, 88 | "A newtyped [`rand_xoshiro::Xoshiro256StarStar`] RNG", 89 | "rand_xoshiro" 90 | ); 91 | 92 | newtype_prng!( 93 | Xoshiro256PlusPlus, 94 | ::rand_xoshiro::Xoshiro256PlusPlus, 95 | "A newtyped [`rand_xoshiro::Xoshiro256PlusPlus`] RNG", 96 | "rand_xoshiro" 97 | ); 98 | 99 | newtype_prng!( 100 | Xoshiro256Plus, 101 | ::rand_xoshiro::Xoshiro256Plus, 102 | "A newtyped [`rand_xoshiro::Xoshiro256Plus`] RNG", 103 | "rand_xoshiro" 104 | ); 105 | 106 | newtype_prng!( 107 | Xoroshiro128StarStar, 108 | ::rand_xoshiro::Xoroshiro128StarStar, 109 | "A newtyped [`rand_xoshiro::Xoshiro128StarStar`] RNG", 110 | "rand_xoshiro" 111 | ); 112 | 113 | newtype_prng!( 114 | Xoroshiro128PlusPlus, 115 | ::rand_xoshiro::Xoroshiro128PlusPlus, 116 | "A newtyped [`rand_xoshiro::Xoshiro256PlusPlus`] RNG", 117 | "rand_xoshiro" 118 | ); 119 | 120 | newtype_prng!( 121 | Xoroshiro128Plus, 122 | ::rand_xoshiro::Xoroshiro128Plus, 123 | "A newtyped [`rand_xoshiro::Xoshiro128Plus`] RNG", 124 | "rand_xoshiro" 125 | ); 126 | 127 | newtype_prng!( 128 | Xoshiro128StarStar, 129 | ::rand_xoshiro::Xoshiro128StarStar, 130 | "A newtyped [`rand_xoshiro::Xoshiro128StarStar`] RNG", 131 | "rand_xoshiro" 132 | ); 133 | 134 | newtype_prng!( 135 | Xoshiro128PlusPlus, 136 | ::rand_xoshiro::Xoshiro128PlusPlus, 137 | "A newtyped [`rand_xoshiro::Xoshiro256PlusPlus`] RNG", 138 | "rand_xoshiro" 139 | ); 140 | 141 | newtype_prng!( 142 | Xoshiro128Plus, 143 | ::rand_xoshiro::Xoshiro128Plus, 144 | "A newtyped [`rand_xoshiro::Xoshiro128Plus`] RNG", 145 | "rand_xoshiro" 146 | ); 147 | 148 | newtype_prng!( 149 | Xoroshiro64StarStar, 150 | ::rand_xoshiro::Xoroshiro64StarStar, 151 | "A newtyped [`rand_xoshiro::Xoroshiro64StarStar`] RNG", 152 | "rand_xoshiro" 153 | ); 154 | 155 | newtype_prng!( 156 | Xoroshiro64Star, 157 | ::rand_xoshiro::Xoroshiro64Star, 158 | "A newtyped [`rand_xoshiro::Xoroshiro64Star`] RNG", 159 | "rand_xoshiro" 160 | ); 161 | -------------------------------------------------------------------------------- /examples/turn_based_game.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::type_complexity)] 2 | 3 | use bevy_app::prelude::*; 4 | use bevy_ecs::prelude::*; 5 | use bevy_prng::ChaCha8Rng; 6 | use bevy_rand::prelude::{Entropy, EntropyPlugin, ForkableRng, GlobalEntropy}; 7 | use rand::prelude::{IteratorRandom, Rng}; 8 | 9 | #[derive(Component, PartialEq, Eq)] 10 | enum Kind { 11 | Player, 12 | Enemy, 13 | } 14 | 15 | #[derive(Component)] 16 | struct Name(pub String); 17 | 18 | #[derive(Component)] 19 | struct Attack { 20 | max: f32, 21 | min: f32, 22 | } 23 | 24 | #[derive(Component)] 25 | struct Defense { 26 | dodge: f64, 27 | armor: f32, 28 | } 29 | 30 | #[derive(Component)] 31 | struct Buff { 32 | effect: f32, 33 | chance: f64, 34 | } 35 | 36 | #[derive(Component)] 37 | struct Health { 38 | amount: f32, 39 | } 40 | 41 | fn main() { 42 | App::new() 43 | .add_plugins(EntropyPlugin::::with_seed([1; 32])) 44 | .add_systems(Startup, (setup_player, setup_enemies).chain()) 45 | .add_systems( 46 | Update, 47 | (determine_attack_order.pipe(attack_turn), buff_entities).chain(), 48 | ) 49 | .run(); 50 | } 51 | 52 | fn setup_player(mut commands: Commands, mut rng: GlobalEntropy) { 53 | commands.spawn(( 54 | Kind::Player, 55 | Name("Player".into()), 56 | Attack { 57 | max: 10.0, 58 | min: 2.0, 59 | }, 60 | Defense { 61 | dodge: 0.25, 62 | armor: 3.0, 63 | }, 64 | Buff { 65 | effect: 5.0, 66 | chance: 0.5, 67 | }, 68 | Health { amount: 50.0 }, 69 | // Forking from the global instance creates a random, but deterministic 70 | // seed for the component, making it hard to guess yet still have a 71 | // deterministic output 72 | rng.fork_rng(), 73 | )); 74 | } 75 | 76 | fn setup_enemies(mut commands: Commands, mut rng: GlobalEntropy) { 77 | for i in 1..=2 { 78 | commands.spawn(( 79 | Kind::Enemy, 80 | Name(format!("Goblin {i}")), 81 | Attack { max: 8.0, min: 1.0 }, 82 | Defense { 83 | dodge: 0.2, 84 | armor: 2.5, 85 | }, 86 | Buff { 87 | effect: 5.0, 88 | chance: 0.25, 89 | }, 90 | Health { amount: 20.0 }, 91 | // Forking from the global instance creates a random, but deterministic 92 | // seed for the component, making it hard to guess yet still have a 93 | // deterministic output 94 | rng.fork_rng(), 95 | )); 96 | } 97 | } 98 | 99 | fn determine_attack_order( 100 | mut q_entities: Query<(Entity, &mut Entropy), With>, 101 | ) -> Vec { 102 | // No matter the order of entities in the query, because they have their own RNG instance, 103 | // it will always result in a deterministic output due to being seeded from a single global 104 | // RNG instance with a chosen seed. 105 | let mut entities: Vec<_> = q_entities 106 | .iter_mut() 107 | .map(|mut entity| (entity.1.random::(), entity)) 108 | .collect(); 109 | 110 | entities.sort_by_key(|k| k.0); 111 | 112 | entities.iter_mut().map(|(_, entity)| entity.0).collect() 113 | } 114 | 115 | fn attack_turn( 116 | In(attack_order): In>, 117 | mut q_entities: Query<( 118 | Entity, 119 | &Kind, 120 | &Attack, 121 | &Defense, 122 | &Name, 123 | &mut Health, 124 | &mut Entropy, 125 | )>, 126 | ) { 127 | // Establish list of enemy entities for player to attack 128 | let enemies: Vec<_> = q_entities 129 | .iter() 130 | .filter_map(|entity| entity.1.eq(&Kind::Enemy).then_some(entity.0)) 131 | .collect(); 132 | 133 | // Get the Player entity for the enemies to target 134 | let player = q_entities 135 | .iter() 136 | .find_map(|entity| entity.1.eq(&Kind::Player).then_some(entity.0)) 137 | .unwrap(); 138 | 139 | // We've created a sorted attack order from another system, so this should always be deterministic. 140 | for entity in attack_order { 141 | // Calculate the target and the amount of damage to attempt to apply to the target. 142 | let (target, attack_damage, attacker) = { 143 | let (_, attacker, attack, _, name, _, mut a_rng) = q_entities.get_mut(entity).unwrap(); 144 | 145 | let attack_damage = a_rng.random_range(attack.min..=attack.max); 146 | 147 | let target = if attacker == &Kind::Player { 148 | enemies.iter().choose(a_rng.as_mut()).copied().unwrap() 149 | } else { 150 | player 151 | }; 152 | 153 | (target, attack_damage, name.0.clone()) 154 | }; 155 | 156 | // Calculate the defense of the target for mitigating the damage. 157 | let (_, _, _, defense, defender, mut hp, mut d_rng) = q_entities.get_mut(target).unwrap(); 158 | 159 | // Will they dodge the attack? 160 | if d_rng.random_bool(defense.dodge) { 161 | println!("{} dodged {}'s attack!", defender.0, attacker); 162 | } else { 163 | let damage_taken = (attack_damage - defense.armor).clamp(0.0, f32::MAX); 164 | 165 | hp.amount = (hp.amount - damage_taken).clamp(0.0, f32::MAX); 166 | 167 | println!( 168 | "{} took {} damage from {}", 169 | defender.0, damage_taken, attacker 170 | ); 171 | } 172 | } 173 | } 174 | 175 | fn buff_entities( 176 | mut q_entities: Query<(&Name, &Buff, &mut Health, &mut Entropy), With>, 177 | ) { 178 | // Query iteration order is not stable, but entities having their own RNG source side-steps this 179 | // completely, so the result is always deterministic. 180 | for (name, buff, mut hp, mut rng) in q_entities.iter_mut() { 181 | if rng.random_bool(buff.chance) { 182 | hp.amount += buff.effect; 183 | 184 | println!("{} buffed their health by {} points!", name.0, buff.effect); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/commands.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | marker::PhantomData, 3 | ops::{Deref, DerefMut}, 4 | }; 5 | 6 | use bevy_ecs::{ 7 | bundle::Bundle, 8 | entity::Entity, 9 | relationship::RelatedSpawnerCommands, 10 | system::{Commands, EntityCommands}, 11 | }; 12 | use bevy_prng::EntropySource; 13 | 14 | use crate::{ 15 | observers::{RngSource, SeedFromGlobal, SeedFromSource, SeedLinked}, 16 | params::RngEntityItem, 17 | seed::RngSeed, 18 | traits::SeedSource, 19 | }; 20 | 21 | /// Commands for handling RNG specific operations with regards to seeding and 22 | /// linking. 23 | pub struct RngEntityCommands<'a, Rng: EntropySource> { 24 | commands: EntityCommands<'a>, 25 | _rng: PhantomData, 26 | } 27 | 28 | /// Extension trait for [`Commands`] for getting access to [`RngEntityCommands`]. 29 | pub trait RngEntityCommandsExt<'a> { 30 | /// Takes an [`Entity`] and yields the [`RngEntityCommands`] for that entity. 31 | /// ``` 32 | /// use bevy_ecs::prelude::*; 33 | /// use bevy_prng::WyRand; 34 | /// use bevy_rand::prelude::*; 35 | /// 36 | /// #[derive(Component)] 37 | /// struct Target; 38 | /// 39 | /// fn intialise_rng_entities(mut commands: Commands, mut q_targets: Query>) { 40 | /// for target in &q_targets { 41 | /// commands.entity(target).rng::().reseed_from_os_rng(); 42 | /// } 43 | /// } 44 | /// ``` 45 | fn rng(self) -> RngEntityCommands<'a, Rng>; 46 | } 47 | 48 | impl<'a> RngEntityCommandsExt<'a> for EntityCommands<'a> { 49 | fn rng(self) -> RngEntityCommands<'a, Rng> { 50 | RngEntityCommands { 51 | commands: self, 52 | _rng: PhantomData, 53 | } 54 | } 55 | } 56 | 57 | impl<'a, Rng: EntropySource> Deref for RngEntityCommands<'a, Rng> { 58 | type Target = EntityCommands<'a>; 59 | 60 | fn deref(&self) -> &Self::Target { 61 | &self.commands 62 | } 63 | } 64 | 65 | impl DerefMut for RngEntityCommands<'_, Rng> { 66 | fn deref_mut(&mut self) -> &mut Self::Target { 67 | &mut self.commands 68 | } 69 | } 70 | 71 | /// Extension trait to create a [`RngEntityCommands`] directly from a [`Commands`]. 72 | pub trait RngCommandsExt { 73 | /// Creates a [`RngEntityCommands`] from a given [`Entity`]. 74 | /// ``` 75 | /// use bevy_ecs::prelude::*; 76 | /// use bevy_rand::prelude::*; 77 | /// use bevy_prng::WyRand; 78 | /// 79 | /// #[derive(Component)] 80 | /// struct Source; 81 | /// 82 | /// fn reseed(mut commands: Commands, query: Query, With>) { 83 | /// for entity in &query { 84 | /// commands.rng_entity(&entity).reseed_linked(); 85 | /// } 86 | /// } 87 | /// ``` 88 | fn rng_entity( 89 | &mut self, 90 | entity: &RngEntityItem<'_, Rng>, 91 | ) -> RngEntityCommands<'_, Rng>; 92 | } 93 | 94 | impl RngCommandsExt for Commands<'_, '_> { 95 | fn rng_entity( 96 | &mut self, 97 | entity: &RngEntityItem<'_, Rng>, 98 | ) -> RngEntityCommands<'_, Rng> { 99 | self.entity(entity.entity()).rng() 100 | } 101 | } 102 | 103 | impl RngEntityCommands<'_, Rng> 104 | where 105 | Rng::Seed: Send + Sync + Clone, 106 | { 107 | /// Reseeds the current `Rng` with a provided seed value. 108 | #[inline] 109 | pub fn reseed(&mut self, seed: Rng::Seed) -> &mut Self { 110 | self.commands.insert(RngSeed::::from_seed(seed)); 111 | 112 | self 113 | } 114 | 115 | /// Reseeds the current `Rng` with a new seed drawn from userspace entropy sources. 116 | /// 117 | /// # Panics 118 | /// 119 | /// Panics if it is unable to source entropy from a user-space source. 120 | #[inline] 121 | #[cfg(feature = "thread_local_entropy")] 122 | pub fn reseed_from_local_entropy(&mut self) -> &mut Self { 123 | self.commands.insert(RngSeed::::from_local_entropy()); 124 | 125 | self 126 | } 127 | 128 | /// Reseeds the current `Rng` with a new seed drawn from userspace entropy sources. 129 | #[inline] 130 | #[cfg(feature = "thread_local_entropy")] 131 | pub fn try_reseed_from_local_entropy(&mut self) -> Result<&mut Self, std::thread::AccessError> { 132 | self.commands 133 | .insert(RngSeed::::try_from_local_entropy()?); 134 | 135 | Ok(self) 136 | } 137 | 138 | /// Reseeds the current `Rng` with a new seed drawn from OS sources. 139 | /// 140 | /// # Panics 141 | /// 142 | /// Panics if it is unable to source entropy from an OS/Hardware source. 143 | #[inline] 144 | pub fn reseed_from_os_rng(&mut self) -> &mut Self { 145 | self.commands.insert(RngSeed::::from_os_rng()); 146 | 147 | self 148 | } 149 | 150 | /// Reseeds the current `Rng` with a new seed drawn from OS sources. 151 | #[inline] 152 | pub fn try_reseed_from_os_rng(&mut self) -> Result<&mut Self, rand_core::OsError> { 153 | self.commands.insert(RngSeed::::try_from_os_rng()?); 154 | 155 | Ok(self) 156 | } 157 | } 158 | 159 | impl RngEntityCommands<'_, Rng> { 160 | /// Spawns entities related to the current `Source` Rng, linking them and then seeding 161 | /// them automatically. 162 | /// ``` 163 | /// use bevy_ecs::prelude::*; 164 | /// use bevy_rand::prelude::*; 165 | /// use bevy_prng::WyRand; 166 | /// 167 | /// #[derive(Component)] 168 | /// struct Source; 169 | /// #[derive(Component)] 170 | /// struct Target; 171 | /// 172 | /// fn setup_rng_sources(mut global: GlobalRngEntity) { 173 | /// global.rng_commands().with_target_rngs([( 174 | /// Source, 175 | /// RngLinks::::spawn(( 176 | /// Spawn(Target), 177 | /// Spawn(Target), 178 | /// Spawn(Target), 179 | /// Spawn(Target), 180 | /// Spawn(Target), 181 | /// )), 182 | /// )]); 183 | /// } 184 | /// ``` 185 | #[inline] 186 | pub fn with_target_rngs( 187 | &mut self, 188 | targets: impl IntoIterator, 189 | ) -> &mut Self { 190 | self.with_target_rngs_as::(targets) 191 | } 192 | 193 | /// Spawns entities related to the current Source Rng, linking them and then seeding 194 | /// them automatically with the specified `Target` Rng. 195 | /// 196 | /// ``` 197 | /// use bevy_ecs::prelude::*; 198 | /// use bevy_rand::prelude::*; 199 | /// use bevy_prng::{ChaCha8Rng, WyRand}; 200 | /// 201 | /// #[derive(Component)] 202 | /// struct Source; 203 | /// #[derive(Component)] 204 | /// struct Target; 205 | /// 206 | /// fn setup_rng_sources(mut global: GlobalRngEntity) { 207 | /// global.rng_commands().with_target_rngs_as::([( 208 | /// Source, 209 | /// RngLinks::::spawn(( 210 | /// Spawn(Target), 211 | /// Spawn(Target), 212 | /// Spawn(Target), 213 | /// Spawn(Target), 214 | /// Spawn(Target), 215 | /// )), 216 | /// )]); 217 | /// } 218 | /// ``` 219 | pub fn with_target_rngs_as( 220 | &mut self, 221 | targets: impl IntoIterator, 222 | ) -> &mut Self { 223 | self.commands.with_related_entities( 224 | |related: &mut RelatedSpawnerCommands<'_, RngSource>| { 225 | targets.into_iter().for_each(|bundle| { 226 | related.spawn(bundle); 227 | }); 228 | }, 229 | ); 230 | 231 | self.reseed_linked_as::() 232 | } 233 | 234 | /// Links a list of target [`Entity`]s to the current `Rng`, designating it 235 | /// as the Source `Rng` for the Targets to draw new seeds from. 236 | #[inline] 237 | pub fn link_target_rngs(&mut self, targets: &[Entity]) -> &mut Self { 238 | self.commands.add_related::>(targets); 239 | 240 | self 241 | } 242 | 243 | /// Links a list of target [`Entity`]s to the current `Rng` as the specified `Target` type, 244 | /// designating it as the Source `Rng` for the Targets to draw new seeds from. 245 | pub fn link_target_rngs_as(&mut self, targets: &[Entity]) -> &mut Self { 246 | self.commands.add_related::>(targets); 247 | 248 | self 249 | } 250 | 251 | /// Emits an event for the current Source `Rng` to generate and push out new seeds to 252 | /// all linked target `Rng`s. 253 | #[inline] 254 | pub fn reseed_linked(&mut self) -> &mut Self { 255 | self.reseed_linked_as::() 256 | } 257 | 258 | /// Emits an event for the current Source `Rng` to generate and push out new seeds to 259 | /// all linked target `Rng`s as the specified `Target` type. 260 | pub fn reseed_linked_as(&mut self) -> &mut Self { 261 | self.commands.trigger(SeedLinked::::default()); 262 | 263 | self 264 | } 265 | 266 | /// Emits an event for the current `Rng` to pull a new seed from its linked 267 | /// Source `Rng`. This method assumes the `Source` and `Target` are the same `Rng` 268 | /// type. 269 | #[inline] 270 | pub fn reseed_from_source(&mut self) -> &mut Self { 271 | self.commands.trigger(SeedFromSource::::default()); 272 | 273 | self 274 | } 275 | 276 | /// Emits an event for the current `Rng` to pull a new seed from its linked 277 | /// Source `Rng`. A `Rng` entity can have multiple linked sources, so a source 278 | /// `Rng` must be specified explicitly if you want to pull from a `Source` that 279 | /// isn't the same `Rng` kind as the target. 280 | pub fn reseed_from_source_as(&mut self) -> &mut Self { 281 | self.commands 282 | .trigger(SeedFromSource::::default()); 283 | 284 | self 285 | } 286 | 287 | /// Emits an event for the current `Rng` to pull a new seed from the specified 288 | /// Global `Rng`. 289 | #[inline] 290 | pub fn reseed_from_global(&mut self) -> &mut Self { 291 | self.commands.trigger(SeedFromGlobal::::default()); 292 | 293 | self 294 | } 295 | 296 | /// Emits an event for the current `Rng` to pull a new seed from the specified 297 | /// Global `Rng`. 298 | pub fn reseed_from_global_as(&mut self) -> &mut Self { 299 | self.commands 300 | .trigger(SeedFromGlobal::::default()); 301 | 302 | self 303 | } 304 | 305 | /// Returns the inner [`EntityCommands`] with a smaller lifetime. 306 | #[inline] 307 | pub fn entity_commands(&mut self) -> EntityCommands<'_> { 308 | self.commands.reborrow() 309 | } 310 | 311 | /// Returns the underlying [`Commands`]. 312 | #[inline] 313 | pub fn commands(&mut self) -> Commands<'_, '_> { 314 | self.commands.commands() 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/component.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | 3 | use crate::{ 4 | seed::RngSeed, 5 | traits::{ 6 | EcsEntropy, ForkableAsRng, ForkableAsSeed, ForkableInnerRng, ForkableInnerSeed, 7 | ForkableRng, ForkableSeed, 8 | }, 9 | }; 10 | use bevy_ecs::prelude::Component; 11 | #[cfg(feature = "bevy_reflect")] 12 | use bevy_ecs::prelude::ReflectComponent; 13 | use bevy_prng::EntropySource; 14 | #[cfg(feature = "bevy_reflect")] 15 | use bevy_reflect::{Reflect, ReflectFromReflect}; 16 | use rand_core::{RngCore, SeedableRng, TryRngCore}; 17 | 18 | #[cfg(feature = "thread_local_entropy")] 19 | use crate::thread_local_entropy::ThreadLocalEntropy; 20 | 21 | #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] 22 | use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; 23 | 24 | #[cfg(feature = "serialize")] 25 | use serde::Deserialize; 26 | 27 | /// An [`Entropy`] that wraps a random number generator that implements 28 | /// [`RngCore`] & [`SeedableRng`]. 29 | /// 30 | /// ## Creating new [`Entropy`]s. 31 | /// 32 | /// You can creates a new [`Entropy`] directly from anything that implements 33 | /// [`RngCore`] or provides a mut reference to [`RngCore`], such as a 34 | /// [`Component`], or from a [`RngCore`] source directly. 35 | /// 36 | /// ## Examples 37 | /// 38 | /// Randomised Component: 39 | /// ``` 40 | /// use bevy_ecs::prelude::*; 41 | /// use bevy_prng::WyRand; 42 | /// use bevy_rand::prelude::Entropy; 43 | /// 44 | /// #[derive(Component)] 45 | /// struct Source; 46 | /// 47 | /// fn setup_source(mut commands: Commands) { 48 | /// commands 49 | /// .spawn(( 50 | /// Source, 51 | /// Entropy::::default(), 52 | /// )); 53 | /// } 54 | /// ``` 55 | /// 56 | /// Seeded from a resource: 57 | /// ``` 58 | /// use bevy_ecs::prelude::*; 59 | /// use bevy_prng::ChaCha8Rng; 60 | /// use bevy_rand::prelude::{GlobalEntropy, ForkableRng}; 61 | /// 62 | /// #[derive(Component)] 63 | /// struct Source; 64 | /// 65 | /// fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 66 | /// commands 67 | /// .spawn(( 68 | /// Source, 69 | /// global.fork_rng(), 70 | /// )); 71 | /// } 72 | /// ``` 73 | /// 74 | /// Seeded from a component: 75 | /// ``` 76 | /// use bevy_ecs::prelude::*; 77 | /// use bevy_prng::WyRand; 78 | /// use bevy_rand::prelude::{Entropy, ForkableRng}; 79 | /// 80 | /// #[derive(Component)] 81 | /// struct Npc; 82 | /// #[derive(Component)] 83 | /// struct Source; 84 | /// 85 | /// fn setup_npc_from_source( 86 | /// mut commands: Commands, 87 | /// mut q_source: Single<&mut Entropy, (With, Without)>, 88 | /// ) { 89 | /// let mut source = q_source.into_inner(); 90 | /// 91 | /// for _ in 0..2 { 92 | /// commands 93 | /// .spawn(( 94 | /// Npc, 95 | /// source.fork_rng() 96 | /// )); 97 | /// } 98 | /// } 99 | /// ``` 100 | #[derive(Debug, Clone, PartialEq, Eq, Component)] 101 | #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] 102 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 103 | #[cfg_attr( 104 | feature = "serialize", 105 | serde(bound(deserialize = "R: for<'a> Deserialize<'a>")) 106 | )] 107 | #[cfg_attr( 108 | all(feature = "serialize", feature = "bevy_reflect"), 109 | reflect(Debug, PartialEq, Component, FromReflect, Serialize, Deserialize) 110 | )] 111 | #[cfg_attr( 112 | all(not(feature = "serialize"), feature = "bevy_reflect"), 113 | reflect(Debug, PartialEq, Component, FromReflect) 114 | )] 115 | pub struct Entropy(R); 116 | 117 | impl Entropy { 118 | /// Create a new component from an `RngCore` instance. 119 | #[inline] 120 | #[must_use] 121 | pub fn new(rng: R) -> Self { 122 | Self(rng) 123 | } 124 | 125 | /// Reseeds the internal `RngCore` instance with a new seed. 126 | #[inline] 127 | #[deprecated = "Make use of `RngSeed` component instead for reseeding."] 128 | pub fn reseed(&mut self, seed: R::Seed) { 129 | self.0 = R::from_seed(seed); 130 | } 131 | } 132 | 133 | impl Default for Entropy { 134 | #[inline] 135 | fn default() -> Self { 136 | #[cfg(feature = "thread_local_entropy")] 137 | { 138 | let mut local = 139 | ThreadLocalEntropy::new().expect("Unable to source entropy for initialisation"); 140 | Self::from_rng(&mut local) 141 | } 142 | #[cfg(not(feature = "thread_local_entropy"))] 143 | { 144 | Self::from_os_rng() 145 | } 146 | } 147 | } 148 | 149 | impl RngCore for Entropy { 150 | #[inline] 151 | fn next_u32(&mut self) -> u32 { 152 | self.0.next_u32() 153 | } 154 | 155 | #[inline] 156 | fn next_u64(&mut self) -> u64 { 157 | self.0.next_u64() 158 | } 159 | 160 | #[inline] 161 | fn fill_bytes(&mut self, dest: &mut [u8]) { 162 | self.0.fill_bytes(dest); 163 | } 164 | } 165 | 166 | #[cfg(feature = "compat")] 167 | impl rand_core_06::RngCore for Entropy { 168 | #[inline] 169 | fn next_u32(&mut self) -> u32 { 170 | self.0.next_u32() 171 | } 172 | 173 | #[inline] 174 | fn next_u64(&mut self) -> u64 { 175 | self.0.next_u64() 176 | } 177 | 178 | #[inline] 179 | fn fill_bytes(&mut self, dest: &mut [u8]) { 180 | self.0.fill_bytes(dest); 181 | } 182 | 183 | #[inline] 184 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { 185 | self.0.fill_bytes(dest); 186 | Ok(()) 187 | } 188 | } 189 | 190 | impl SeedableRng for Entropy { 191 | type Seed = R::Seed; 192 | 193 | #[inline] 194 | fn from_seed(seed: Self::Seed) -> Self { 195 | Self::new(R::from_seed(seed)) 196 | } 197 | 198 | #[inline] 199 | fn from_rng(rng: &mut impl RngCore) -> Self { 200 | Self::new(R::from_rng(rng)) 201 | } 202 | 203 | #[inline] 204 | fn try_from_rng(rng: &mut T) -> Result { 205 | Ok(Self::new(R::try_from_rng(rng)?)) 206 | } 207 | } 208 | 209 | impl EcsEntropy for Entropy {} 210 | 211 | impl ForkableRng for Entropy 212 | where 213 | R: EntropySource, 214 | { 215 | type Output = Entropy; 216 | } 217 | 218 | impl ForkableAsRng for Entropy 219 | where 220 | R: EntropySource, 221 | { 222 | type Output 223 | = Entropy 224 | where 225 | T: EntropySource; 226 | } 227 | 228 | impl ForkableInnerRng for Entropy 229 | where 230 | R: EntropySource, 231 | { 232 | type Output = R; 233 | } 234 | 235 | impl ForkableSeed for Entropy 236 | where 237 | R: EntropySource, 238 | R::Seed: Send + Sync + Clone, 239 | { 240 | type Output = RngSeed; 241 | } 242 | 243 | impl ForkableAsSeed for Entropy 244 | where 245 | R: EntropySource, 246 | { 247 | type Output 248 | = RngSeed 249 | where 250 | T: EntropySource, 251 | T::Seed: Send + Sync + Clone; 252 | } 253 | 254 | impl ForkableInnerSeed for Entropy 255 | where 256 | R: EntropySource, 257 | R::Seed: Send + Sync + Clone + AsMut<[u8]> + Default, 258 | { 259 | type Output = R::Seed; 260 | } 261 | 262 | #[cfg(test)] 263 | mod tests { 264 | use alloc::format; 265 | 266 | use bevy_prng::{ChaCha8Rng, ChaCha12Rng}; 267 | 268 | use super::*; 269 | 270 | #[test] 271 | fn forking() { 272 | let mut rng1 = Entropy::::default(); 273 | 274 | let rng2 = rng1.fork_rng(); 275 | 276 | assert_ne!(rng1, rng2, "forked Entropys should not match each other"); 277 | } 278 | 279 | #[test] 280 | fn forking_as() { 281 | let mut rng1 = Entropy::::default(); 282 | 283 | let rng2 = rng1.fork_as::(); 284 | 285 | let rng1 = format!("{rng1:?}"); 286 | let rng2 = format!("{rng2:?}"); 287 | 288 | assert_ne!(&rng1, &rng2, "forked Entropys should not match each other"); 289 | } 290 | 291 | #[test] 292 | fn forking_inner() { 293 | let mut rng1 = Entropy::::default(); 294 | 295 | let rng2 = rng1.fork_inner(); 296 | 297 | assert_ne!( 298 | rng1.0, rng2, 299 | "forked ChaCha8Rngs should not match each other" 300 | ); 301 | } 302 | 303 | #[cfg(feature = "bevy_reflect")] 304 | #[test] 305 | fn type_paths() { 306 | use bevy_reflect::TypePath; 307 | 308 | assert_eq!( 309 | "bevy_rand::component::Entropy", 310 | Entropy::::type_path() 311 | ); 312 | 313 | assert_eq!( 314 | "Entropy", 315 | Entropy::::short_type_path() 316 | ); 317 | } 318 | 319 | #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] 320 | #[test] 321 | fn rng_untyped_serialization() { 322 | use bevy_reflect::{ 323 | FromReflect, TypeRegistry, 324 | serde::{ReflectDeserializer, ReflectSerializer}, 325 | }; 326 | use ron::to_string; 327 | use serde::de::DeserializeSeed; 328 | 329 | let mut registry = TypeRegistry::default(); 330 | registry.register::>(); 331 | 332 | let mut val: Entropy = Entropy::from_seed([7; 32]); 333 | 334 | // Modify the state of the RNG instance 335 | val.next_u32(); 336 | 337 | let ser = ReflectSerializer::new(&val, ®istry); 338 | 339 | let serialized = to_string(&ser).unwrap(); 340 | 341 | assert_eq!( 342 | &serialized, 343 | "{\"bevy_rand::component::Entropy\":(((seed:(7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7),stream:0,word_pos:1)))}" 344 | ); 345 | 346 | let mut deserializer = ron::Deserializer::from_str(&serialized).unwrap(); 347 | 348 | let de = ReflectDeserializer::new(®istry); 349 | 350 | let value = de.deserialize(&mut deserializer).unwrap(); 351 | 352 | let mut dynamic = Entropy::::take_from_reflect(value).unwrap(); 353 | 354 | // The two instances should be the same 355 | assert_eq!( 356 | val, dynamic, 357 | "The deserialized Entropy should equal the original" 358 | ); 359 | // They should output the same numbers, as no state is lost between serialization and deserialization. 360 | assert_eq!( 361 | val.next_u32(), 362 | dynamic.next_u32(), 363 | "The deserialized Entropy should have the same output as original" 364 | ); 365 | } 366 | 367 | #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] 368 | #[test] 369 | fn rng_typed_serialization() { 370 | use bevy_reflect::{ 371 | FromReflect, GetTypeRegistration, TypeRegistry, 372 | serde::{TypedReflectDeserializer, TypedReflectSerializer}, 373 | }; 374 | use ron::ser::to_string; 375 | use serde::de::DeserializeSeed; 376 | 377 | let mut registry = TypeRegistry::default(); 378 | registry.register::>(); 379 | 380 | let registered_type = Entropy::::get_type_registration(); 381 | 382 | let mut val = Entropy::::from_seed([7; 32]); 383 | 384 | // Modify the state of the RNG instance 385 | val.next_u32(); 386 | 387 | let ser = TypedReflectSerializer::new(&val, ®istry); 388 | 389 | let serialized = to_string(&ser).unwrap(); 390 | 391 | assert_eq!( 392 | &serialized, 393 | "(((seed:(7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7),stream:0,word_pos:1)))" 394 | ); 395 | 396 | let mut deserializer = ron::Deserializer::from_str(&serialized).unwrap(); 397 | 398 | let de = TypedReflectDeserializer::new(®istered_type, ®istry); 399 | 400 | let value = de.deserialize(&mut deserializer).unwrap(); 401 | 402 | let mut dynamic = Entropy::::take_from_reflect(value).unwrap(); 403 | 404 | // The two instances should be the same 405 | assert_eq!( 406 | val, dynamic, 407 | "The deserialized Entropy should equal the original" 408 | ); 409 | // They should output the same numbers, as no state is lost between serialization and deserialization. 410 | assert_eq!( 411 | val.next_u32(), 412 | dynamic.next_u32(), 413 | "The deserialized Entropy should have the same output as original" 414 | ); 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /src/global.rs: -------------------------------------------------------------------------------- 1 | use core::{fmt::Debug, ops::Deref}; 2 | 3 | use bevy_ecs::{ 4 | component::Component, 5 | query::With, 6 | system::{Commands, Single, SystemParam}, 7 | }; 8 | use bevy_prng::EntropySource; 9 | 10 | use crate::{ 11 | params::{RngEntity, RngEntityItem}, 12 | prelude::{Entropy, RngEntityCommands, RngEntityCommandsExt}, 13 | }; 14 | 15 | /// A marker component to signify a global source. Warning: there should only be **one** entity per 16 | /// PRNG type that qualifies as the `Global` source. 17 | #[derive(Debug, Component)] 18 | pub struct Global; 19 | 20 | /// A helper query to yield the [`Global`] source for a given [`bevy_prng::EntropySource`]. This returns the 21 | /// [`Entropy`] component to generate new random numbers from. 22 | pub type GlobalEntropy<'w, T> = Single<'w, &'static mut Entropy, With>; 23 | 24 | /// A helper [`SystemParam`] to obtain the [`Global`] entity & seed of a given `Rng`. This yields 25 | /// read-only access to the global entity and its seed, and also allows constructing a 26 | /// [`RngEntityCommands`] directly from it. 27 | /// ``` 28 | /// use bevy_ecs::prelude::*; 29 | /// use bevy_rand::prelude::*; 30 | /// use bevy_prng::WyRand; 31 | /// 32 | /// fn reseed_all_linked_rngs_from_global(mut global: GlobalRngEntity) { 33 | /// global.rng_commands().reseed_linked(); 34 | /// } 35 | /// ``` 36 | #[derive(SystemParam)] 37 | pub struct GlobalRngEntity<'w, 's, Rng: EntropySource> { 38 | commands: Commands<'w, 's>, 39 | data: Single<'w, RngEntity, With>, 40 | } 41 | 42 | impl GlobalRngEntity<'_, '_, Rng> { 43 | /// Creates a [`Global`]'s [`RngEntityCommands`]. 44 | pub fn rng_commands(&mut self) -> RngEntityCommands<'_, Rng> { 45 | self.commands.entity(self.data.entity()).rng() 46 | } 47 | } 48 | 49 | impl<'w, Rng: EntropySource> Deref for GlobalRngEntity<'w, '_, Rng> { 50 | type Target = RngEntityItem<'w, Rng>; 51 | 52 | #[inline] 53 | fn deref(&self) -> &Self::Target { 54 | &self.data 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::type_complexity)] 2 | #![warn(clippy::undocumented_unsafe_blocks)] 3 | #![cfg_attr(docsrs, feature(doc_cfg))] 4 | #![cfg_attr(docsrs, allow(unused_attributes))] 5 | #![warn(missing_docs)] 6 | #![no_std] 7 | #![doc = include_str!("../README.md")] 8 | 9 | extern crate alloc; 10 | 11 | #[cfg(feature = "std")] 12 | extern crate std; 13 | 14 | /// Command extensions for relating groups of RNGs. 15 | pub mod commands; 16 | /// Components for integrating [`rand_core::RngCore`] PRNGs into bevy. Must be newtyped to support [`bevy_reflect::Reflect`]. 17 | pub mod component; 18 | /// Global [`crate::component::Entropy`] sources, with query helpers. 19 | pub mod global; 20 | /// Utility observers for handling seeding between parent/child entropy sources 21 | pub mod observers; 22 | /// Utility query/system parameters for accessing RNGs. 23 | pub mod params; 24 | /// Plugin for integrating [`rand_core::RngCore`] PRNGs into bevy. Must be newtyped to support [`bevy_reflect::Reflect`]. 25 | pub mod plugin; 26 | /// Prelude for providing all necessary types for easy use. 27 | pub mod prelude; 28 | /// Seed Components for seeding [`crate::component::Entropy`]. 29 | pub mod seed; 30 | #[cfg(feature = "thread_local_entropy")] 31 | mod thread_local_entropy; 32 | /// Traits for enabling utility methods for [`crate::component::Entropy`]. 33 | pub mod traits; 34 | #[cfg(doc)] 35 | pub mod tutorial; 36 | -------------------------------------------------------------------------------- /src/observers.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use core::{fmt::Debug, marker::PhantomData}; 3 | 4 | use bevy_ecs::{ 5 | prelude::{Commands, Component, Entity, Event, OnInsert, Trigger, With}, 6 | system::Query, 7 | }; 8 | 9 | use bevy_prng::EntropySource; 10 | 11 | use crate::{ 12 | params::RngEntity, 13 | prelude::{Entropy, GlobalEntropy, RngCommandsExt}, 14 | traits::ForkableAsSeed, 15 | }; 16 | 17 | /// Component to denote a source has linked children entities 18 | #[derive(Debug, Component)] 19 | #[relationship_target(relationship = RngSource)] 20 | pub struct RngLinks { 21 | #[relationship] 22 | related: Vec, 23 | _source: PhantomData, 24 | _target: PhantomData, 25 | } 26 | 27 | impl Default for RngLinks { 28 | #[inline] 29 | fn default() -> Self { 30 | Self { 31 | related: Vec::new(), 32 | _source: PhantomData, 33 | _target: PhantomData, 34 | } 35 | } 36 | } 37 | 38 | /// Component to denote that the current Entity has a relation to a parent Rng source entity. 39 | #[derive(Debug, Component)] 40 | #[relationship(relationship_target = RngLinks)] 41 | pub struct RngSource { 42 | #[relationship] 43 | linked: Entity, 44 | _source: PhantomData, 45 | _target: PhantomData, 46 | } 47 | 48 | impl RngSource { 49 | /// Initialises the relation component with the parent entity 50 | #[inline] 51 | pub fn new(parent: Entity) -> Self { 52 | Self { 53 | linked: parent, 54 | _source: PhantomData, 55 | _target: PhantomData, 56 | } 57 | } 58 | 59 | /// Get the parent source entity 60 | #[inline] 61 | pub fn entity(&self) -> Entity { 62 | self.linked 63 | } 64 | } 65 | 66 | /// Observer event for triggering an entity to pull a new seed value from a 67 | /// GlobalEntropy source. 68 | #[derive(Debug, Event)] 69 | pub struct SeedFromGlobal(PhantomData, PhantomData); 70 | 71 | impl Default for SeedFromGlobal { 72 | #[inline] 73 | fn default() -> Self { 74 | Self(PhantomData, PhantomData) 75 | } 76 | } 77 | 78 | /// Observer event for triggering an entity to pull a new seed value from a 79 | /// GlobalEntropy source. 80 | #[derive(Debug, Event)] 81 | pub struct SeedLinked(PhantomData, PhantomData); 82 | 83 | impl Default for SeedLinked { 84 | #[inline] 85 | fn default() -> Self { 86 | Self(PhantomData, PhantomData) 87 | } 88 | } 89 | 90 | /// Observer event for triggering an entity to pull a new seed value from a 91 | /// linked parent entity. 92 | #[derive(Debug, Event)] 93 | pub struct SeedFromSource(PhantomData, PhantomData); 94 | 95 | impl Default for SeedFromSource { 96 | #[inline] 97 | fn default() -> Self { 98 | Self(PhantomData, PhantomData) 99 | } 100 | } 101 | 102 | /// Observer System for pulling in a new seed from a GlobalEntropy source 103 | pub fn seed_from_global( 104 | trigger: Trigger>, 105 | mut source: GlobalEntropy, 106 | mut commands: Commands, 107 | ) { 108 | if let Ok(mut entity) = commands.get_entity(trigger.target()) { 109 | entity.insert(source.fork_as_seed::()); 110 | } 111 | } 112 | 113 | /// Observer System for pulling in a new seed for the current entity from its parent Rng source. This 114 | /// observer system will only run if there are parent entities to have seeds pulled from. 115 | pub fn seed_from_parent( 116 | trigger: Trigger>, 117 | q_linked: Query<&RngSource>, 118 | mut q_parents: Query<&mut Entropy, With>>, 119 | mut commands: Commands, 120 | ) { 121 | let target = trigger.target(); 122 | 123 | if let Ok(rng) = q_linked 124 | .get(target) 125 | .and_then(|parent| q_parents.get_mut(parent.entity())) 126 | .map(|mut rng| rng.fork_as_seed::()) 127 | { 128 | // This won't panic, because we've already checked in the .get above whether `target` exists. 129 | commands.entity(target).insert(rng); 130 | } 131 | } 132 | 133 | /// Observer System for handling seed propagation from source Rng to all child entities. This observer 134 | /// will only run if there is a source entity and also if there are target entities to seed. 135 | pub fn seed_linked( 136 | trigger: Trigger>, 137 | mut q_source: Query<(&mut Entropy, &RngLinks)>, 138 | mut commands: Commands, 139 | ) { 140 | if let Ok((mut rng, targets)) = q_source.get_mut(trigger.target()) { 141 | let batched: Vec<_> = targets 142 | .related 143 | .iter() 144 | .copied() 145 | .map(|target| (target, rng.fork_as_seed::())) 146 | .collect(); 147 | 148 | commands.insert_batch(batched); 149 | } 150 | } 151 | 152 | /// Observer System for triggering seed propagation from source Rng to all child entities. This observer 153 | /// will only run if there is a source entity and also if there are target entities to seed. 154 | pub fn trigger_seed_linked( 155 | trigger: Trigger>, 156 | q_source: Query, With>>, 157 | mut commands: Commands, 158 | ) { 159 | // Check whether the triggered entity is a source entity. If not, do nothing otherwise we 160 | // will keep triggering and cause a stack overflow. 161 | if let Ok(mut rng_source) = q_source 162 | .get(trigger.target()) 163 | .map(|source| commands.rng_entity(&source)) 164 | { 165 | rng_source.reseed_linked_as::(); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/params.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | 3 | use bevy_ecs::{entity::Entity, query::QueryData}; 4 | use bevy_prng::EntropySource; 5 | 6 | use crate::{seed::RngSeed, traits::SeedSource}; 7 | 8 | /// A smart query data wrapper that selects for entities that match the `Rng` type, 9 | /// returning read-only data for the seed and the entity. 10 | #[derive(Debug, QueryData)] 11 | pub struct RngEntity { 12 | seed: &'static RngSeed, 13 | entity: Entity, 14 | } 15 | 16 | impl RngEntityItem<'_, Rng> { 17 | /// Return the [`Entity`] of the data 18 | #[inline] 19 | pub fn entity(&self) -> Entity { 20 | self.entity 21 | } 22 | 23 | /// Get a reference to the [`RngSeed`] component for the given data 24 | #[inline] 25 | pub fn seed(&self) -> &RngSeed { 26 | self.seed 27 | } 28 | 29 | /// Clone the seed from the data 30 | #[inline] 31 | pub fn clone_seed(&self) -> Rng::Seed { 32 | self.seed.clone_seed() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/plugin.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use crate::{global::Global, seed::RngSeed, traits::SeedSource}; 4 | use bevy_app::{App, Plugin}; 5 | use bevy_prng::{EntropySeed, EntropySource}; 6 | 7 | /// Plugin for integrating a PRNG that implements `RngCore` into 8 | /// the bevy engine, registering types for a global resource and 9 | /// entropy components. 10 | /// 11 | /// ``` 12 | /// use bevy_app::prelude::*; 13 | /// use bevy_ecs::prelude::*; 14 | /// use bevy_prng::{ChaCha8Rng, WyRand}; 15 | /// use bevy_rand::prelude::{EntropyPlugin, GlobalEntropy}; 16 | /// use rand_core::RngCore; 17 | /// 18 | /// fn main() { 19 | /// App::new() 20 | /// .add_plugins(( 21 | /// EntropyPlugin::::default(), 22 | /// EntropyPlugin::::default() 23 | /// )) 24 | /// .add_systems(Update, print_random_value) 25 | /// .run(); 26 | /// } 27 | /// 28 | /// fn print_random_value(mut rng: GlobalEntropy) { 29 | /// println!("Random value: {}", rng.next_u32()); 30 | /// } 31 | /// ``` 32 | pub struct EntropyPlugin { 33 | seed: Option, 34 | } 35 | 36 | impl EntropyPlugin { 37 | /// Creates a new plugin instance configured for randomised, 38 | /// non-deterministic seeding of the global entropy resource. 39 | #[inline] 40 | #[must_use] 41 | pub fn new() -> Self { 42 | Self { seed: None } 43 | } 44 | 45 | /// Configures the plugin instance to have a set seed for the 46 | /// global entropy resource. 47 | #[inline] 48 | pub fn with_seed(seed: Rng::Seed) -> Self { 49 | Self { seed: Some(seed) } 50 | } 51 | } 52 | 53 | impl Default for EntropyPlugin { 54 | fn default() -> Self { 55 | Self::new() 56 | } 57 | } 58 | 59 | impl Plugin for EntropyPlugin 60 | where 61 | Rng::Seed: EntropySeed, 62 | { 63 | fn build(&self, app: &mut App) { 64 | #[cfg(feature = "bevy_reflect")] 65 | app.register_type::>() 66 | .register_type::>() 67 | .register_type::(); 68 | 69 | let world = app.world_mut(); 70 | 71 | world.register_component_hooks::>(); 72 | 73 | world.spawn(( 74 | self.seed 75 | .clone() 76 | .map_or_else(RngSeed::::default, RngSeed::::from_seed), 77 | Global, 78 | )); 79 | 80 | world.flush(); 81 | } 82 | } 83 | 84 | /// [`Plugin`] for setting up relations/observers for handling related Rngs. It takes two generic parameters, 85 | /// the first is the `Source` Rng, which is the algorithm for the source Rng entity, and then the second 86 | /// is the `Target` Rng, which is the algorithm for the targets. It follows a One to One/Many relationship 87 | /// model, going from `Source` to `Target`, where `Source` can have one or many `Target`s. 88 | /// 89 | /// Note: This is for RNG algorithms, not Components. For more information, please read the 90 | /// [tutorial](https://docs.rs/bevy_rand/latest/bevy_rand/tutorial/ch05_observer_driven_reseeding/index.html). 91 | /// 92 | /// ``` 93 | /// use bevy_app::prelude::*; 94 | /// use bevy_prng::{ChaCha8Rng, WyRand}; 95 | /// use bevy_rand::prelude::{EntropyPlugin, EntropyRelationsPlugin}; 96 | /// 97 | /// App::new() 98 | /// .add_plugins(( 99 | /// // First initialise the RNGs 100 | /// EntropyPlugin::::default(), 101 | /// EntropyPlugin::::default(), 102 | /// // This initialises observers for WyRand -> WyRand seeding relations 103 | /// EntropyRelationsPlugin::::default(), 104 | /// // This initialises observers for ChaCha8Rng -> WyRand seeding relations 105 | /// EntropyRelationsPlugin::::default(), 106 | /// )) 107 | /// .run(); 108 | /// ``` 109 | pub struct EntropyRelationsPlugin { 110 | _source: PhantomData, 111 | _target: PhantomData, 112 | } 113 | 114 | impl Default 115 | for EntropyRelationsPlugin 116 | { 117 | fn default() -> Self { 118 | Self { 119 | _source: PhantomData, 120 | _target: PhantomData, 121 | } 122 | } 123 | } 124 | 125 | impl Plugin 126 | for EntropyRelationsPlugin 127 | { 128 | fn build(&self, app: &mut App) { 129 | let world = app.world_mut(); 130 | 131 | world.add_observer(crate::observers::seed_from_global::); 132 | world.add_observer(crate::observers::seed_from_parent::); 133 | world.add_observer(crate::observers::seed_linked::); 134 | world.add_observer(crate::observers::trigger_seed_linked::); 135 | 136 | world.flush(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::commands::{RngCommandsExt, RngEntityCommands, RngEntityCommandsExt}; 2 | pub use crate::component::Entropy; 3 | pub use crate::global::{Global, GlobalEntropy, GlobalRngEntity}; 4 | pub use crate::observers::{RngLinks, RngSource, SeedFromGlobal, SeedFromSource, SeedLinked}; 5 | pub use crate::params::{RngEntity, RngEntityItem}; 6 | pub use crate::plugin::{EntropyPlugin, EntropyRelationsPlugin}; 7 | pub use crate::seed::RngSeed; 8 | pub use crate::traits::{ 9 | ForkRngExt, ForkSeedExt, ForkableAsRng, ForkableAsSeed, ForkableInnerRng, ForkableInnerSeed, 10 | ForkableRng, ForkableSeed, SeedSource, 11 | }; 12 | #[cfg(feature = "wyrand")] 13 | #[cfg_attr(docsrs, doc(cfg(feature = "wyrand")))] 14 | pub use bevy_prng::WyRand; 15 | 16 | #[cfg(feature = "rand_chacha")] 17 | #[cfg_attr(docsrs, doc(cfg(feature = "rand_chacha")))] 18 | pub use bevy_prng::{ChaCha8Rng, ChaCha12Rng, ChaCha20Rng}; 19 | 20 | #[cfg(feature = "rand_pcg")] 21 | #[cfg_attr(docsrs, doc(cfg(feature = "rand_pcg")))] 22 | pub use bevy_prng::{Pcg32, Pcg64, Pcg64Mcg}; 23 | 24 | #[cfg(feature = "rand_xoshiro")] 25 | #[cfg_attr(docsrs, doc(cfg(feature = "rand_xoshiro")))] 26 | pub use bevy_prng::{ 27 | Xoroshiro64Star, Xoroshiro64StarStar, Xoroshiro128Plus, Xoroshiro128PlusPlus, 28 | Xoroshiro128StarStar, Xoshiro128Plus, Xoshiro128PlusPlus, Xoshiro128StarStar, Xoshiro256Plus, 29 | Xoshiro256PlusPlus, Xoshiro256StarStar, Xoshiro512Plus, Xoshiro512PlusPlus, Xoshiro512StarStar, 30 | }; 31 | 32 | #[cfg(all(feature = "rand_xoshiro", feature = "bevy_reflect"))] 33 | #[cfg_attr( 34 | docsrs, 35 | doc(cfg(all(feature = "rand_xoshiro", feature = "rand_xoshiro"))) 36 | )] 37 | pub use bevy_prng::Seed512; 38 | -------------------------------------------------------------------------------- /src/seed.rs: -------------------------------------------------------------------------------- 1 | use core::{marker::PhantomData, ops::Deref}; 2 | 3 | use bevy_ecs::{ 4 | component::{Immutable, StorageType}, 5 | prelude::Component, 6 | }; 7 | use bevy_prng::EntropySource; 8 | #[cfg(feature = "bevy_reflect")] 9 | use bevy_reflect::Reflect; 10 | use rand_core::SeedableRng; 11 | 12 | use crate::{component::Entropy, traits::SeedSource}; 13 | 14 | /// The initial seed/state for an [`Entropy`]. Adding this component to an `Entity` will cause 15 | /// an `Entropy` to be initialised as well. To force a reseed, just insert this component to an 16 | /// `Entity` to overwrite the old value, and the `Entropy` will be overwritten with the new seed 17 | /// in turn. 18 | /// 19 | /// ## Examples 20 | /// 21 | /// Randomised Seed via `Default`: 22 | /// ``` 23 | /// use bevy_ecs::prelude::*; 24 | /// use bevy_prng::WyRand; 25 | /// use bevy_rand::prelude::RngSeed; 26 | /// 27 | /// #[derive(Component)] 28 | /// struct Source; 29 | /// 30 | /// fn setup_source(mut commands: Commands) { 31 | /// commands 32 | /// .spawn(( 33 | /// Source, 34 | /// RngSeed::::default(), 35 | /// )); 36 | /// } 37 | /// ``` 38 | #[derive(Debug, Clone, PartialEq, Eq)] 39 | #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] 40 | pub struct RngSeed { 41 | seed: R::Seed, 42 | #[cfg_attr(feature = "bevy_reflect", reflect(ignore))] 43 | rng: PhantomData, 44 | } 45 | 46 | impl SeedSource for RngSeed 47 | where 48 | R::Seed: Sync + Send + Clone, 49 | { 50 | /// Create a new instance of [`RngSeed`] from a given `seed` value. 51 | #[inline] 52 | fn from_seed(seed: R::Seed) -> Self { 53 | Self { 54 | seed, 55 | rng: PhantomData, 56 | } 57 | } 58 | 59 | #[inline] 60 | fn get_seed(&self) -> &R::Seed { 61 | &self.seed 62 | } 63 | 64 | #[inline] 65 | fn clone_seed(&self) -> R::Seed { 66 | self.seed.clone() 67 | } 68 | } 69 | 70 | impl Component for RngSeed 71 | where 72 | R::Seed: Sync + Send + Clone, 73 | { 74 | const STORAGE_TYPE: StorageType = StorageType::Table; 75 | type Mutability = Immutable; 76 | 77 | fn on_insert() -> Option { 78 | Some(|mut world, context| { 79 | let seed = world 80 | .get::>(context.entity) 81 | .map(|seed| seed.clone_seed()) 82 | .unwrap(); 83 | world 84 | .commands() 85 | .entity(context.entity) 86 | .insert(Entropy::::from_seed(seed)); 87 | }) 88 | } 89 | 90 | fn on_remove() -> Option { 91 | Some(|mut world, context| { 92 | world 93 | .commands() 94 | .entity(context.entity) 95 | .remove::>(); 96 | }) 97 | } 98 | } 99 | 100 | impl Default for RngSeed 101 | where 102 | R::Seed: Sync + Send + Clone, 103 | { 104 | #[inline] 105 | fn default() -> Self { 106 | #[cfg(feature = "thread_local_entropy")] 107 | { 108 | Self::from_local_entropy() 109 | } 110 | #[cfg(not(feature = "thread_local_entropy"))] 111 | { 112 | Self::from_os_rng() 113 | } 114 | } 115 | } 116 | 117 | impl Deref for RngSeed 118 | where 119 | R::Seed: Sync + Send + Clone, 120 | { 121 | type Target = R::Seed; 122 | 123 | #[inline] 124 | fn deref(&self) -> &Self::Target { 125 | self.get_seed() 126 | } 127 | } 128 | 129 | #[cfg(test)] 130 | mod tests { 131 | #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] 132 | #[test] 133 | fn reflection_serialization_round_trip_works() { 134 | use super::*; 135 | 136 | use bevy_prng::WyRand; 137 | use bevy_reflect::{ 138 | FromReflect, GetTypeRegistration, TypeRegistry, 139 | serde::{TypedReflectDeserializer, TypedReflectSerializer}, 140 | }; 141 | use ron::to_string; 142 | use serde::de::DeserializeSeed; 143 | 144 | let mut registry = TypeRegistry::default(); 145 | registry.register::>(); 146 | registry.register::<[u8; 8]>(); 147 | 148 | let registered_type = RngSeed::::get_type_registration(); 149 | 150 | let val = RngSeed::::from_seed(u64::MAX.to_ne_bytes()); 151 | 152 | let ser = TypedReflectSerializer::new(&val, ®istry); 153 | 154 | let serialized = to_string(&ser).unwrap(); 155 | 156 | assert_eq!(&serialized, "(seed:(255,255,255,255,255,255,255,255))"); 157 | 158 | let mut deserializer = ron::Deserializer::from_str(&serialized).unwrap(); 159 | 160 | let de = TypedReflectDeserializer::new(®istered_type, ®istry); 161 | 162 | let value = de.deserialize(&mut deserializer).unwrap(); 163 | 164 | assert!(value.is_dynamic()); 165 | assert!(value.represents::>()); 166 | assert!(value.try_downcast_ref::>().is_none()); 167 | 168 | let recreated = RngSeed::::from_reflect(value.as_ref()).unwrap(); 169 | 170 | assert_eq!(val.clone_seed(), recreated.clone_seed()); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/thread_local_entropy.rs: -------------------------------------------------------------------------------- 1 | use alloc::rc::Rc; 2 | use core::cell::UnsafeCell; 3 | 4 | use std::thread_local; 5 | 6 | use rand_chacha::ChaCha8Rng; 7 | use rand_core::{CryptoRng, RngCore, SeedableRng}; 8 | 9 | thread_local! { 10 | // We require `Rc` to avoid premature freeing when `ThreadLocalEntropy` is used within thread-local destructors. 11 | static SOURCE: Rc> = Rc::new(UnsafeCell::new(ChaCha8Rng::from_os_rng())); 12 | } 13 | 14 | /// [`ThreadLocalEntropy`] uses thread local [`ChaCha8Rng`] instances to provide faster alternative for 15 | /// sourcing entropy to OS/Hardware sources. The use of `ChaCha8` with 8 rounds as opposed to 12 or 20 rounds 16 | /// is due to tuning for additional speed/throughput. While this does minimise the quality of the entropy, 17 | /// the output should still be sufficiently secure as per the recommendations set in the 18 | /// [Too Much Crypto](https://eprint.iacr.org/2019/1492.pdf) paper. [`ThreadLocalEntropy`] is not thread-safe and 19 | /// cannot be sent or synchronised between threads, it should be initialised within each thread context it is 20 | /// needed in. 21 | pub(crate) struct ThreadLocalEntropy(Rc>); 22 | 23 | impl ThreadLocalEntropy { 24 | /// Create a new [`ThreadLocalEntropy`] instance. 25 | pub(crate) fn new() -> Result { 26 | Ok(Self(SOURCE.try_with(Rc::clone)?)) 27 | } 28 | 29 | /// Initiates an access to the thread local source, passing a `&mut ChaCha8Rng` to the 30 | /// closure. 31 | #[inline(always)] 32 | fn access_local_source(&mut self, f: F) -> O 33 | where 34 | F: FnOnce(&mut ChaCha8Rng) -> O, 35 | { 36 | // SAFETY: The `&mut` reference constructed here will never outlive the closure 37 | // for the thread local access. It is also will never be a null pointer and is aligned. 38 | unsafe { f(&mut *self.0.get()) } 39 | } 40 | } 41 | 42 | impl core::fmt::Debug for ThreadLocalEntropy { 43 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 44 | f.debug_tuple("ThreadLocalEntropy").finish() 45 | } 46 | } 47 | 48 | impl RngCore for ThreadLocalEntropy { 49 | #[inline] 50 | fn next_u32(&mut self) -> u32 { 51 | self.access_local_source(RngCore::next_u32) 52 | } 53 | 54 | #[inline] 55 | fn next_u64(&mut self) -> u64 { 56 | self.access_local_source(RngCore::next_u64) 57 | } 58 | 59 | #[inline] 60 | fn fill_bytes(&mut self, dest: &mut [u8]) { 61 | self.access_local_source(|rng| rng.fill_bytes(dest)); 62 | } 63 | } 64 | 65 | impl CryptoRng for ThreadLocalEntropy {} 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use alloc::{format, vec, vec::Vec}; 70 | 71 | use super::*; 72 | 73 | #[test] 74 | fn smoke_test() -> Result<(), std::thread::AccessError> { 75 | let mut rng1 = ThreadLocalEntropy::new()?; 76 | let mut rng2 = ThreadLocalEntropy::new()?; 77 | 78 | // Neither instance should interfere with each other 79 | rng1.next_u32(); 80 | rng2.next_u64(); 81 | 82 | let mut bytes1 = vec![0u8; 128]; 83 | let mut bytes2 = vec![0u8; 128]; 84 | 85 | rng1.fill_bytes(&mut bytes1); 86 | rng2.fill_bytes(&mut bytes2); 87 | 88 | // ThreadLocalEntropy instances won't output the same entropy as the 89 | // underlying thread local source gets mutated each access. 90 | assert_ne!(&bytes1, &bytes2); 91 | 92 | Ok(()) 93 | } 94 | 95 | #[test] 96 | fn unique_source_per_thread() { 97 | let mut bytes1: Vec = vec![0u8; 128]; 98 | let mut bytes2: Vec = vec![0u8; 128]; 99 | 100 | let b1 = bytes1.as_mut(); 101 | let b2 = bytes2.as_mut(); 102 | 103 | let (a, b) = std::thread::scope(|s| { 104 | let a = s.spawn(move || { 105 | // Obtain a thread local entropy source from this thread context. 106 | // It should be initialised with a random state. 107 | let mut rng = 108 | ThreadLocalEntropy::new().expect("Should not fail when accessing local source"); 109 | 110 | // Use the source to produce some stored entropy. 111 | rng.fill_bytes(b1); 112 | 113 | rng.access_local_source(|rng| rng.clone()) 114 | }); 115 | let b = s.spawn(move || { 116 | // Obtain a thread local entropy source from this thread context. 117 | // It should be initialised with a random state. 118 | let mut rng = 119 | ThreadLocalEntropy::new().expect("Should not fail when accessing local source"); 120 | 121 | // Use the source to produce some stored entropy. 122 | rng.fill_bytes(b2); 123 | 124 | rng.access_local_source(|rng| rng.clone()) 125 | }); 126 | 127 | (a.join(), b.join()) 128 | }); 129 | 130 | let a = a.unwrap(); 131 | let b = b.unwrap(); 132 | 133 | // The references to the thread local RNG sources will not be 134 | // the same, as they each were initialised with different random 135 | // states to each other from OS sources, even though each went 136 | // through the exact same deterministic steps to fill some bytes. 137 | // If the tasks ran on the same thread, then the RNG sources should 138 | // be in different resulting states as the same source was advanced 139 | // further. 140 | assert_ne!(&a, &b); 141 | 142 | // Double check the entropy output in each buffer is not the same either. 143 | assert_ne!(&bytes1, &bytes2); 144 | } 145 | 146 | #[test] 147 | fn non_leaking_debug() { 148 | assert_eq!( 149 | "Ok(ThreadLocalEntropy)", 150 | format!("{:?}", ThreadLocalEntropy::new()) 151 | ); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::{ 2 | query::{QuerySingleError, With}, 3 | world::World, 4 | }; 5 | use bevy_prng::EntropySource; 6 | use rand_core::{OsRng, RngCore, SeedableRng, TryRngCore}; 7 | 8 | use crate::{global::Global, prelude::Entropy, seed::RngSeed}; 9 | 10 | /// Trait for implementing Forking behaviour for [`crate::component::Entropy`]. 11 | /// Forking creates a new RNG instance using a generated seed from the original source. If the original is seeded with a known 12 | /// seed, this process is deterministic. 13 | pub trait ForkableRng: EcsEntropy { 14 | /// The type of instance that is to be forked from the original source. 15 | type Output: EcsEntropy; 16 | 17 | /// Fork the original instance to yield a new instance with a generated seed. 18 | /// This method preserves the RNG algorithm between original and forked instances. 19 | /// ``` 20 | /// use bevy_ecs::prelude::*; 21 | /// use bevy_prng::ChaCha8Rng; 22 | /// use bevy_rand::prelude::{GlobalEntropy, ForkableRng}; 23 | /// 24 | /// #[derive(Component)] 25 | /// struct Source; 26 | /// 27 | /// fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 28 | /// commands 29 | /// .spawn(( 30 | /// Source, 31 | /// global.fork_rng(), 32 | /// )); 33 | /// } 34 | /// ``` 35 | #[inline] 36 | fn fork_rng(&mut self) -> Self::Output { 37 | Self::Output::from_rng(self) 38 | } 39 | } 40 | 41 | /// Trait for implementing Forking behaviour for [`crate::component::Entropy`]. 42 | /// Forking creates a new RNG instance using a generated seed from the original source. If the original is seeded with a known 43 | /// seed, this process is deterministic. This trait enables forking between different PRNG algorithm types. 44 | pub trait ForkableAsRng: EcsEntropy { 45 | /// The type of instance that is to be forked from the original source. 46 | type Output: EcsEntropy 47 | where 48 | R: EntropySource; 49 | 50 | /// Fork the original instance to yield a new instance with a generated seed. 51 | /// This method allows one to specify the RNG algorithm to be used for the forked instance. 52 | /// ``` 53 | /// use bevy_ecs::prelude::*; 54 | /// use bevy_rand::prelude::{GlobalEntropy, ForkableAsRng}; 55 | /// use bevy_prng::{ChaCha8Rng, ChaCha12Rng}; 56 | /// 57 | /// #[derive(Component)] 58 | /// struct Source; 59 | /// 60 | /// fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 61 | /// commands 62 | /// .spawn(( 63 | /// Source, 64 | /// global.fork_as::(), 65 | /// )); 66 | /// } 67 | /// ``` 68 | #[inline] 69 | fn fork_as(&mut self) -> Self::Output { 70 | Self::Output::<_>::from_rng(self) 71 | } 72 | } 73 | 74 | /// Trait for implementing Forking behaviour for [`crate::component::Entropy`]. 75 | /// Forking creates a new RNG instance using a generated seed from the original source. If the original is seeded with a known 76 | /// seed, this process is deterministic. This trait enables forking the inner PRNG instance of the source component/resource. 77 | pub trait ForkableInnerRng: EcsEntropy { 78 | /// The type of instance that is to be forked from the original source. 79 | type Output: EntropySource; 80 | 81 | /// Fork the original instance to yield a new instance with a generated seed. 82 | /// This method yields the inner PRNG instance directly as a forked instance. 83 | /// ``` 84 | /// use bevy_ecs::prelude::*; 85 | /// use bevy_rand::prelude::{GlobalEntropy, ForkableInnerRng}; 86 | /// use bevy_prng::ChaCha8Rng; 87 | /// use rand_core::RngCore; 88 | /// 89 | /// #[derive(Component)] 90 | /// struct Source; 91 | /// 92 | /// fn do_random_action(source: &mut ChaCha8Rng) { 93 | /// println!("Random value: {}", source.next_u32()); 94 | /// } 95 | /// 96 | /// fn access_source(mut global: GlobalEntropy) { 97 | /// let mut source = global.fork_inner(); 98 | /// 99 | /// do_random_action(&mut source); 100 | /// } 101 | /// ``` 102 | #[inline] 103 | fn fork_inner(&mut self) -> Self::Output { 104 | Self::Output::from_rng(self) 105 | } 106 | } 107 | 108 | /// Trait for implementing forking behaviour for [`crate::component::Entropy`]. 109 | /// Forking creates a new RNG instance using a generated seed from the original source. If the original is seeded with a known 110 | /// seed, this process is deterministic. This trait enables forking from an entropy source to a seed component. 111 | pub trait ForkableSeed: EcsEntropy { 112 | /// The type of seed component that is to be forked from the original source. 113 | type Output: SeedSource; 114 | 115 | /// Fork a new seed from the original entropy source. 116 | /// This method preserves the RNG algorithm between original instance and forked seed. 117 | /// ``` 118 | /// use bevy_ecs::prelude::*; 119 | /// use bevy_prng::ChaCha8Rng; 120 | /// use bevy_rand::prelude::{GlobalEntropy, ForkableSeed}; 121 | /// 122 | /// #[derive(Component)] 123 | /// struct Source; 124 | /// 125 | /// fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 126 | /// commands 127 | /// .spawn(( 128 | /// Source, 129 | /// global.fork_seed(), 130 | /// )); 131 | /// } 132 | /// ``` 133 | #[inline] 134 | fn fork_seed(&mut self) -> Self::Output { 135 | let mut seed = S::Seed::default(); 136 | 137 | self.fill_bytes(seed.as_mut()); 138 | 139 | Self::Output::from_seed(seed) 140 | } 141 | } 142 | 143 | /// Trait for implementing Forking behaviour for [`crate::component::Entropy`]. 144 | /// Forking creates a new RNG instance using a generated seed from the original source. If the original is seeded with a known 145 | /// seed, this process is deterministic. This trait enables forking from an entropy source to a seed component of a different 146 | /// PRNG algorithm. 147 | pub trait ForkableAsSeed: EcsEntropy { 148 | /// The type of seed component that is to be forked from the original source. 149 | type Output: SeedSource 150 | where 151 | T: EntropySource; 152 | 153 | /// Fork a new seed from the original entropy source. 154 | /// This method allows one to specify the RNG algorithm to be used for the forked seed. 155 | /// ``` 156 | /// use bevy_ecs::prelude::*; 157 | /// use bevy_rand::prelude::{GlobalEntropy, ForkableAsSeed}; 158 | /// use bevy_prng::{ChaCha8Rng, ChaCha12Rng}; 159 | /// 160 | /// #[derive(Component)] 161 | /// struct Source; 162 | /// 163 | /// fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 164 | /// commands 165 | /// .spawn(( 166 | /// Source, 167 | /// global.fork_as_seed::(), 168 | /// )); 169 | /// } 170 | /// ``` 171 | #[inline] 172 | fn fork_as_seed(&mut self) -> Self::Output { 173 | let mut seed = T::Seed::default(); 174 | 175 | self.fill_bytes(seed.as_mut()); 176 | 177 | Self::Output::::from_seed(seed) 178 | } 179 | } 180 | 181 | /// Trait for implementing forking behaviour for [`crate::component::Entropy`]. 182 | /// Forking creates a new RNG instance using a generated seed from the original source. If the original is seeded with a known 183 | /// seed, this process is deterministic. This trait enables forking from an entropy source to the RNG's seed type. 184 | pub trait ForkableInnerSeed: EcsEntropy { 185 | /// The type of seed component that is to be forked from the original source. 186 | type Output: Send + Sync + Clone + AsMut<[u8]> + Default; 187 | 188 | /// Fork a new seed from the original entropy source. 189 | /// This method preserves the RNG algorithm between original instance and forked seed. 190 | /// ``` 191 | /// use bevy_ecs::prelude::*; 192 | /// use bevy_prng::ChaCha8Rng; 193 | /// use bevy_rand::prelude::{GlobalEntropy, ForkableInnerSeed, SeedSource, RngSeed}; 194 | /// 195 | /// #[derive(Component)] 196 | /// struct Source; 197 | /// 198 | /// fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 199 | /// commands 200 | /// .spawn(( 201 | /// Source, 202 | /// RngSeed::::from_seed(global.fork_inner_seed()), 203 | /// )); 204 | /// } 205 | /// ``` 206 | #[inline] 207 | fn fork_inner_seed(&mut self) -> Self::Output { 208 | let mut seed = Self::Output::default(); 209 | 210 | self.fill_bytes(seed.as_mut()); 211 | 212 | seed 213 | } 214 | } 215 | 216 | /// A trait for providing [`crate::seed::RngSeed`] with 217 | /// common initialization strategies. This trait is not object safe and is also a sealed trait. 218 | pub trait SeedSource: private::SealedSeed { 219 | /// Initialize a [`SeedSource`] from a given `seed` value. 220 | fn from_seed(seed: R::Seed) -> Self; 221 | 222 | /// Returns a reference of the seed value. 223 | fn get_seed(&self) -> &R::Seed; 224 | 225 | /// Returns a cloned instance of the seed value. 226 | fn clone_seed(&self) -> R::Seed; 227 | 228 | /// Initialize a [`SeedSource`] from a `seed` value obtained from a 229 | /// user-space RNG source. 230 | /// 231 | /// # Panics 232 | /// 233 | /// This method panics if for whatever reason it is unable to source entropy 234 | /// from the User-Space/OS/Hardware source. 235 | #[deprecated = "use either `SeedSource::from_local_entropy` or `SeedSource::from_os_rng` instead"] 236 | fn from_entropy() -> Self 237 | where 238 | Self: Sized, 239 | { 240 | #[cfg(feature = "thread_local_entropy")] 241 | { 242 | Self::from_local_entropy() 243 | } 244 | #[cfg(not(feature = "thread_local_entropy"))] 245 | { 246 | Self::from_os_rng() 247 | } 248 | } 249 | 250 | /// Initialize a [`SeedSource`] from a `seed` value obtained from a 251 | /// user-space RNG source. This is usually much, much faster than sourcing 252 | /// entropy from OS/Hardware sources. 253 | #[cfg(feature = "thread_local_entropy")] 254 | fn try_from_local_entropy() -> Result 255 | where 256 | Self: Sized, 257 | { 258 | let mut dest = R::Seed::default(); 259 | 260 | crate::thread_local_entropy::ThreadLocalEntropy::new()?.fill_bytes(dest.as_mut()); 261 | 262 | Ok(Self::from_seed(dest)) 263 | } 264 | 265 | /// Initialize a [`SeedSource`] from a `seed` value obtained from a 266 | /// user-space RNG source. This is usually much, much faster than sourcing 267 | /// entropy from OS/Hardware sources. 268 | /// 269 | /// # Panics 270 | /// 271 | /// This method panics if for whatever reason it is unable to source entropy 272 | /// from the User-Space source. 273 | #[cfg(feature = "thread_local_entropy")] 274 | #[inline] 275 | fn from_local_entropy() -> Self 276 | where 277 | Self: Sized, 278 | { 279 | Self::try_from_local_entropy().expect("Unable to source user-space entropy for seeding") 280 | } 281 | 282 | /// Initialize a [`SeedSource`] from a `seed` value obtained from an 283 | /// OS/Hardware RNG source. 284 | fn try_from_os_rng() -> Result 285 | where 286 | Self: Sized, 287 | { 288 | let mut dest = R::Seed::default(); 289 | 290 | OsRng.try_fill_bytes(dest.as_mut())?; 291 | 292 | Ok(Self::from_seed(dest)) 293 | } 294 | 295 | /// Initialize a [`SeedSource`] from a `seed` value obtained from an 296 | /// OS/Hardware RNG source. 297 | /// 298 | /// # Panics 299 | /// 300 | /// This method panics if for whatever reason it is unable to source entropy 301 | /// from an OS/Hardware source. 302 | #[inline] 303 | fn from_os_rng() -> Self 304 | where 305 | Self: Sized, 306 | { 307 | Self::try_from_os_rng().expect("Unable to source os/hardware entropy for seeding") 308 | } 309 | } 310 | 311 | /// Extension trait to allow implementing forking on more types. By default, it is implemented 312 | /// for `&mut World` which sources from [`Global`] source, though this can be manually implemented for more. 313 | pub trait ForkRngExt { 314 | /// The Error type returned for the queries used to extract and fork from. 315 | type Error: core::error::Error; 316 | /// The Output type for the resulting fork methods. Usually will be a `Result`. 317 | type Output; 318 | 319 | /// Forks an [`Entropy`] component from the source. 320 | fn fork_rng(&mut self) -> Self::Output>; 321 | /// Forks an [`Entropy`] component from the source as the given `Target` Rng kind. 322 | fn fork_as( 323 | &mut self, 324 | ) -> Self::Output>; 325 | /// Forks the inner Rng from the source. 326 | fn fork_inner(&mut self) -> Self::Output; 327 | } 328 | 329 | /// Extension trait to allow implementing forking seeds on more types. By default, it is implemented 330 | /// for `&mut World` which sources from [`Global`] source, though this can be manually implemented for more. 331 | pub trait ForkSeedExt { 332 | /// The Error type returned for the queries used to extract and fork from. 333 | type Error: core::error::Error; 334 | /// The Output type for the resulting fork methods. Usually will be a `Result`. 335 | type Output; 336 | 337 | /// Forks a [`RngSeed`] component from the source. 338 | fn fork_seed(&mut self) -> Self::Output>; 339 | /// Forks an [`RngSeed`] component from the source as the given `Target` Rng kind. 340 | fn fork_as_seed( 341 | &mut self, 342 | ) -> Self::Output>; 343 | /// Forks a new Seed from the source. 344 | fn fork_inner_seed(&mut self) -> Self::Output; 345 | } 346 | 347 | impl ForkRngExt for &mut World { 348 | type Error = QuerySingleError; 349 | type Output = Result; 350 | 351 | /// Forks an [`Entropy`] component from the [`Global`] source. 352 | #[inline] 353 | fn fork_rng(&mut self) -> Self::Output> { 354 | self.fork_as::() 355 | } 356 | 357 | /// Forks an [`Entropy`] component from the [`Global`] source as the given `Target` Rng kind. 358 | fn fork_as( 359 | &mut self, 360 | ) -> Self::Output> { 361 | self.query_filtered::<&mut Entropy, With>() 362 | .single_mut(self) 363 | .map(|mut global| global.fork_as::()) 364 | } 365 | 366 | /// Forks the inner Rng from the [`Global`] source. 367 | fn fork_inner(&mut self) -> Self::Output { 368 | self.query_filtered::<&mut Entropy, With>() 369 | .single_mut(self) 370 | .map(|mut global| global.fork_inner()) 371 | } 372 | } 373 | 374 | impl ForkSeedExt for &mut World { 375 | type Error = QuerySingleError; 376 | type Output = Result; 377 | 378 | /// Forks a [`RngSeed`] component from the [`Global`] source. 379 | #[inline] 380 | fn fork_seed(&mut self) -> Self::Output> { 381 | self.fork_as_seed::() 382 | } 383 | 384 | /// Forks an [`RngSeed`] component from the [`Global`] source as the given `Target` Rng kind. 385 | fn fork_as_seed( 386 | &mut self, 387 | ) -> Self::Output> { 388 | self.query_filtered::<&mut Entropy, With>() 389 | .single_mut(self) 390 | .map(|mut global| global.fork_as_seed::()) 391 | } 392 | 393 | /// Forks a new Seed from the [`Global`] source. 394 | fn fork_inner_seed(&mut self) -> Self::Output { 395 | self.query_filtered::<&mut Entropy, With>() 396 | .single_mut(self) 397 | .map(|mut global| global.fork_inner_seed()) 398 | } 399 | } 400 | 401 | /// A marker trait for [`crate::component::Entropy`]. 402 | /// This is a sealed trait and cannot be consumed by downstream. 403 | pub trait EcsEntropy: RngCore + SeedableRng + private::SealedSource {} 404 | 405 | mod private { 406 | use super::{EcsEntropy, EntropySource, SeedSource}; 407 | 408 | pub trait SealedSource {} 409 | pub trait SealedSeed 410 | where 411 | R: EntropySource, 412 | { 413 | } 414 | 415 | impl SealedSource for T where T: EcsEntropy {} 416 | impl SealedSeed for T 417 | where 418 | T: SeedSource, 419 | R: EntropySource, 420 | R::Seed: Send + Sync + Clone, 421 | { 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /src/tutorial.rs: -------------------------------------------------------------------------------- 1 | //! # `bevy_rand` Tutorial 2 | //! 3 | //! This is a tutorial covering the various parts of how to use the crate effectively, 4 | //! from simple usage to more advanced patterns and concepts. 5 | //! 6 | //! Start at [chapter 0](ch00_overview) if you want to know more why `bevy_rand` exists, 7 | //! else if you just want to learn how to use this crate, start at [chapter 1](ch01_choosing_prng) 8 | //! instead. 9 | //! 10 | //! Note: The content provided is not stable and may change with new versions. 11 | 12 | #[doc = include_str!("../tutorial/00-overview.md")] 13 | pub mod ch00_overview {} 14 | 15 | #[doc = include_str!("../tutorial/01-choosing-prng.md")] 16 | pub mod ch01_choosing_prng {} 17 | 18 | #[doc = include_str!("../tutorial/02-basic-usage.md")] 19 | pub mod ch02_basic_usage {} 20 | 21 | #[doc = include_str!("../tutorial/03-components-forking.md")] 22 | pub mod ch03_components_forking {} 23 | 24 | #[doc = include_str!("../tutorial/04-seeding.md")] 25 | pub mod ch04_seeding {} 26 | 27 | #[doc = include_str!("../tutorial/05-observer-driven-reseeding.md")] 28 | pub mod ch05_observer_driven_reseeding {} 29 | -------------------------------------------------------------------------------- /tests/integration/bevy_math.rs: -------------------------------------------------------------------------------- 1 | use bevy_app::{App, Update}; 2 | use bevy_math::{ShapeSample, Vec2, primitives::Circle}; 3 | use bevy_prng::WyRand; 4 | use bevy_rand::{global::GlobalEntropy, plugin::EntropyPlugin}; 5 | use rand::SeedableRng; 6 | 7 | #[cfg(target_arch = "wasm32")] 8 | use wasm_bindgen_test::*; 9 | 10 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 11 | #[test] 12 | fn prng_compatibility() { 13 | let mut source = WyRand::from_seed(42u64.to_ne_bytes()); 14 | 15 | let circle = Circle::new(42.0); 16 | 17 | let boundary = circle.sample_boundary(&mut source); 18 | let interior = circle.sample_interior(&mut source); 19 | 20 | assert_eq!(&boundary, &Vec2::new(-40.885902, 9.609526)); 21 | assert_eq!(&interior, &Vec2::new(-15.362211, 32.41336)); 22 | } 23 | 24 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 25 | #[test] 26 | fn component_compatibility() { 27 | App::new() 28 | .add_plugins(EntropyPlugin::::with_seed(42u64.to_ne_bytes())) 29 | .add_systems(Update, |mut source: GlobalEntropy| { 30 | let circle = Circle::new(42.0); 31 | 32 | let boundary = circle.sample_boundary(source.as_mut()); 33 | let interior = circle.sample_interior(source.as_mut()); 34 | 35 | assert_eq!(&boundary, &Vec2::new(-40.885902, 9.609526)); 36 | assert_eq!(&interior, &Vec2::new(-15.362211, 32.41336)); 37 | }) 38 | .run(); 39 | } 40 | -------------------------------------------------------------------------------- /tests/integration/determinism.rs: -------------------------------------------------------------------------------- 1 | use bevy_app::prelude::*; 2 | use bevy_ecs::prelude::*; 3 | use bevy_prng::{ChaCha8Rng, ChaCha12Rng, WyRand}; 4 | use bevy_rand::prelude::{ 5 | Entropy, EntropyPlugin, ForkableAsRng, ForkableAsSeed, ForkableRng, ForkableSeed, 6 | GlobalEntropy, GlobalRngEntity, 7 | }; 8 | use rand::prelude::Rng; 9 | 10 | use rand_core::RngCore; 11 | 12 | #[cfg(target_arch = "wasm32")] 13 | use wasm_bindgen_test::*; 14 | 15 | #[derive(Component)] 16 | struct SourceA; 17 | 18 | #[derive(Component)] 19 | struct SourceB; 20 | 21 | #[derive(Component)] 22 | struct SourceC; 23 | 24 | #[derive(Component)] 25 | struct SourceD; 26 | 27 | #[derive(Component)] 28 | struct SourceE; 29 | 30 | fn random_output_a(mut rng: Single<&mut Entropy, With>) { 31 | assert_eq!( 32 | rng.random::(), 33 | 3315785188, 34 | "SourceA does not match expected output" 35 | ); 36 | } 37 | 38 | fn random_output_b(mut rng: Single<&mut Entropy, With>) { 39 | assert!( 40 | rng.random_bool(0.5), 41 | "SourceB does not match expected output" 42 | ); 43 | } 44 | 45 | fn random_output_c(mut rng: Single<&mut Entropy, With>) { 46 | assert_eq!( 47 | rng.random_range(0u32..=20u32), 48 | 4, 49 | "SourceC does not match expected output" 50 | ); 51 | } 52 | 53 | fn random_output_d(mut rng: Single<&mut Entropy, With>) { 54 | assert_eq!( 55 | rng.random::<(u16, u16)>(), 56 | (41421, 7891), 57 | "SourceD does not match expected output" 58 | ); 59 | } 60 | 61 | fn random_output_e(mut rng: Single<&mut Entropy, With>) { 62 | let mut bytes = [0u8; 8]; 63 | 64 | rng.fill_bytes(bytes.as_mut()); 65 | 66 | assert_eq!( 67 | &bytes, 68 | &[42, 244, 101, 178, 244, 252, 72, 104], 69 | "SourceE does not match expected output" 70 | ); 71 | } 72 | 73 | fn setup_sources(mut commands: Commands, mut rng: GlobalEntropy) { 74 | commands.spawn((SourceA, rng.fork_rng())); 75 | 76 | commands.spawn((SourceB, rng.fork_seed())); 77 | 78 | commands.spawn((SourceC, rng.fork_rng())); 79 | 80 | commands.spawn((SourceD, rng.fork_as::())); 81 | 82 | commands.spawn((SourceE, rng.fork_as_seed::())); 83 | } 84 | 85 | fn read_global_seed(rng: GlobalRngEntity) { 86 | assert_eq!(rng.clone_seed(), [2; 32]); 87 | } 88 | 89 | /// Entities having their own sources side-steps issues with parallel execution and scheduling 90 | /// not ensuring that certain systems run before others. With an entity having its own RNG source, 91 | /// no matter when the systems that query that entity run, it will always result in a deterministic 92 | /// output. The order of execution will not affect the RNG output, as long as the entities are 93 | /// seeded deterministically and any systems that query a specific entity or group of entities that 94 | /// share the same RNG source are assured to be in order. 95 | /// 96 | /// As an added bonus, this also ensures determinism even when systems are run in single-threaded 97 | /// environments such as WASM. 98 | #[test] 99 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 100 | fn test_parallel_determinism() { 101 | let mut app = App::new(); 102 | 103 | app.add_plugins(EntropyPlugin::::with_seed([2; 32])) 104 | .add_systems(Startup, setup_sources) 105 | .add_systems( 106 | Update, 107 | ( 108 | random_output_a, 109 | random_output_b, 110 | random_output_c, 111 | random_output_d, 112 | random_output_e, 113 | read_global_seed, 114 | ), 115 | ) 116 | .run(); 117 | } 118 | -------------------------------------------------------------------------------- /tests/integration/extension.rs: -------------------------------------------------------------------------------- 1 | use bevy_app::prelude::*; 2 | use bevy_ecs::prelude::*; 3 | use bevy_prng::WyRand; 4 | use bevy_rand::{ 5 | plugin::EntropyPlugin, 6 | traits::{ForkRngExt, ForkSeedExt, SeedSource}, 7 | }; 8 | 9 | use rand::RngCore; 10 | #[cfg(target_arch = "wasm32")] 11 | use wasm_bindgen_test::*; 12 | 13 | #[test] 14 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 15 | fn exclusive_system_forking() { 16 | let mut app = App::new(); 17 | 18 | app.add_plugins(EntropyPlugin::::with_seed(42u64.to_ne_bytes())) 19 | .add_systems(Update, |mut world: &mut World| { 20 | let mut forked = world 21 | .fork_rng::() 22 | .expect("Forking should be successful"); 23 | 24 | assert_eq!(forked.next_u32(), 2755170287); 25 | 26 | let mut inner = world 27 | .fork_inner::() 28 | .expect("Forking should be successful"); 29 | 30 | // Forking should always yield new Rngs with different internal states 31 | assert_ne!(inner.next_u32(), 2755170287); 32 | assert_ne!(forked.next_u32(), inner.next_u32()); 33 | }) 34 | .run(); 35 | } 36 | 37 | #[test] 38 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 39 | fn exclusive_system_forking_seeds() { 40 | let mut app = App::new(); 41 | 42 | app.add_plugins(EntropyPlugin::::with_seed(42u64.to_ne_bytes())) 43 | .add_systems(Update, |mut world: &mut World| { 44 | let forked = world 45 | .fork_seed::() 46 | .expect("Forking should be successful"); 47 | 48 | assert_eq!(forked.get_seed(), &[137, 57, 152, 118, 124, 216, 113, 202]); 49 | 50 | let inner = world 51 | .fork_inner_seed::() 52 | .expect("Forking should be successful"); 53 | 54 | // Forking should always yield new and different seeds 55 | assert_ne!(&inner, &[137, 57, 152, 118, 124, 216, 113, 202]); 56 | assert_ne!(forked.get_seed(), &inner); 57 | }) 58 | .run(); 59 | } 60 | 61 | #[test] 62 | #[should_panic] 63 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 64 | fn exclusive_system_forking_returns_error_without_correct_setup() { 65 | let mut app = App::new(); 66 | 67 | app.add_systems(Update, |mut world: &mut World| { 68 | let mut forked = world 69 | .fork_rng::() 70 | .expect("Forking should be successful"); 71 | 72 | assert_eq!(forked.next_u32(), 2755170287); 73 | }) 74 | .run(); 75 | } 76 | -------------------------------------------------------------------------------- /tests/integration/reseeding.rs: -------------------------------------------------------------------------------- 1 | use bevy_app::prelude::*; 2 | use bevy_ecs::prelude::*; 3 | use bevy_prng::{ChaCha8Rng, WyRand}; 4 | use bevy_rand::{ 5 | global::{Global, GlobalEntropy, GlobalRngEntity}, 6 | params::RngEntity, 7 | plugin::{EntropyPlugin, EntropyRelationsPlugin}, 8 | prelude::{Entropy, RngLinks}, 9 | seed::RngSeed, 10 | traits::{ForkableAsSeed, ForkableSeed, SeedSource}, 11 | }; 12 | use rand_core::{RngCore, SeedableRng}; 13 | 14 | #[cfg(target_arch = "wasm32")] 15 | use wasm_bindgen_test::*; 16 | 17 | #[test] 18 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 19 | pub fn test_global_reseeding() { 20 | let mut app = App::new(); 21 | 22 | let seed = [2; 32]; 23 | 24 | let rng_eq = Entropy::::from_seed(seed); 25 | 26 | app.add_plugins(EntropyPlugin::::with_seed(seed)); 27 | 28 | { 29 | let global_rng = app 30 | .world_mut() 31 | .query_filtered::<&Entropy, With>() 32 | .single(app.world()) 33 | .unwrap(); 34 | 35 | // Our RNGs should be the same as each other as they were initialised with the same seed 36 | assert_eq!(global_rng, &rng_eq); 37 | } 38 | 39 | app.update(); 40 | 41 | { 42 | let global_rng = app 43 | .world_mut() 44 | .query_filtered::<&Entropy, With>() 45 | .single(app.world()) 46 | .unwrap(); 47 | 48 | // Our RNGs should remain the same as each other as we have not run the update 49 | assert_eq!(global_rng, &rng_eq); 50 | } 51 | 52 | { 53 | let global = app 54 | .world_mut() 55 | .query_filtered::>() 56 | .single(app.world()) 57 | .unwrap(); 58 | 59 | app.world_mut() 60 | .entity_mut(global) 61 | .insert(RngSeed::::from_seed([3; 32])); 62 | } 63 | 64 | app.update(); 65 | 66 | { 67 | let global_rng = app 68 | .world_mut() 69 | .query_filtered::<&Entropy, With>() 70 | .single(app.world()) 71 | .unwrap(); 72 | 73 | // Now our RNG will not be the same, even though we did not use it directly 74 | assert_ne!(global_rng, &rng_eq); 75 | } 76 | } 77 | 78 | #[test] 79 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 80 | pub fn component_fork_seed() { 81 | let mut app = App::new(); 82 | 83 | let seed = [2; 32]; 84 | 85 | app.add_plugins(EntropyPlugin::::with_seed(seed)) 86 | .add_systems( 87 | PreStartup, 88 | |mut commands: Commands, mut rng: GlobalEntropy| { 89 | for _ in 0..5 { 90 | commands.spawn(rng.fork_seed()); 91 | } 92 | }, 93 | ) 94 | .add_systems( 95 | Update, 96 | |mut q_rng: Query<&mut Entropy, Without>| { 97 | let rngs = q_rng.iter_mut(); 98 | 99 | assert_eq!(rngs.size_hint().0, 5); 100 | 101 | let values: Vec<_> = rngs.map(|mut rng| rng.next_u32()).collect(); 102 | 103 | assert_eq!( 104 | &values, 105 | &[3315785188, 1951699392, 911252207, 791343233, 1599472206] 106 | ); 107 | }, 108 | ); 109 | 110 | app.update(); 111 | } 112 | 113 | #[test] 114 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 115 | pub fn component_fork_as_seed() { 116 | let mut app = App::new(); 117 | 118 | let seed = [2; 32]; 119 | 120 | app.add_plugins(EntropyPlugin::::with_seed(seed)) 121 | .add_systems( 122 | PreStartup, 123 | |mut commands: Commands, mut rng: GlobalEntropy| { 124 | for _ in 0..5 { 125 | commands.spawn(rng.fork_as_seed::()); 126 | } 127 | }, 128 | ) 129 | .add_systems( 130 | Update, 131 | |mut q_rng: Query<&mut Entropy, Without>| { 132 | let rngs = q_rng.iter_mut(); 133 | 134 | assert_eq!(rngs.size_hint().0, 5); 135 | 136 | let values: Vec<_> = rngs.map(|mut rng| rng.next_u64()).collect(); 137 | 138 | assert_eq!( 139 | &values, 140 | &[ 141 | 10032395693880520184, 142 | 15375025802368380325, 143 | 10863580644061233257, 144 | 7067543572507795213, 145 | 7996461288508244033 146 | ] 147 | ); 148 | }, 149 | ); 150 | 151 | app.update(); 152 | } 153 | 154 | #[test] 155 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 156 | pub fn observer_global_reseeding() { 157 | use bevy_app::prelude::{PostUpdate, PreUpdate, Startup}; 158 | use bevy_rand::traits::SeedSource; 159 | 160 | #[derive(Component, Clone, Copy)] 161 | struct Target; 162 | 163 | let seed = [2; 8]; 164 | 165 | let mut app = App::new(); 166 | 167 | app.add_plugins(( 168 | EntropyPlugin::::with_seed(seed), 169 | EntropyRelationsPlugin::::default(), 170 | )) 171 | .add_systems(Startup, |mut global: GlobalRngEntity| { 172 | global.rng_commands().with_target_rngs([Target; 5]); 173 | }) 174 | .add_systems( 175 | PreUpdate, 176 | |query: Query, Without>| { 177 | let expected = [ 178 | 2484862625678185386u64, 179 | 10323237495534242118, 180 | 14704548354072994214, 181 | 14638519449267265798, 182 | 11723565746675474547, 183 | ]; 184 | let seeds = query.iter().map(|a| a.seed().clone_seed()); 185 | 186 | assert_eq!(seeds.size_hint().0, 5); 187 | 188 | expected 189 | .into_iter() 190 | .zip(seeds.map(u64::from_ne_bytes)) 191 | .for_each(|(expected, actual)| assert_eq!(expected, actual)); 192 | }, 193 | ) 194 | .add_systems(Update, |mut global: GlobalRngEntity| { 195 | global.rng_commands().reseed_linked(); 196 | }) 197 | .add_systems( 198 | PostUpdate, 199 | |query: Query, Without>| { 200 | let prev_expected = [ 201 | 2484862625678185386u64, 202 | 10323237495534242118, 203 | 14704548354072994214, 204 | 14638519449267265798, 205 | 11723565746675474547, 206 | ]; 207 | let seeds = query.iter().map(|rng| rng.clone_seed()); 208 | 209 | assert_eq!(seeds.size_hint().0, 5); 210 | 211 | prev_expected 212 | .into_iter() 213 | .zip(seeds.map(u64::from_ne_bytes)) 214 | .for_each(|(previous, actual)| assert_ne!(previous, actual)); 215 | }, 216 | ); 217 | 218 | app.run(); 219 | } 220 | 221 | #[test] 222 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 223 | pub fn generic_observer_reseeding_from_parent() { 224 | use bevy_app::prelude::{PostUpdate, PreUpdate, Startup}; 225 | use bevy_ecs::prelude::With; 226 | use bevy_rand::{commands::RngCommandsExt, seed::RngSeed, traits::SeedSource}; 227 | 228 | let seed = [2u8; 8]; 229 | 230 | #[derive(Component)] 231 | struct Source; 232 | #[derive(Component)] 233 | struct Target; 234 | 235 | let mut app = App::new(); 236 | 237 | app.add_plugins(( 238 | EntropyPlugin::::with_seed(seed), 239 | EntropyRelationsPlugin::::default(), 240 | )) 241 | .add_systems(Startup, |mut global: GlobalRngEntity| { 242 | global 243 | .rng_commands() 244 | .with_target_rngs([(Source, RngLinks::::spawn(Spawn(Target)))]); 245 | }) 246 | .add_systems( 247 | PreUpdate, 248 | |query: Single, With>| { 249 | let expected = 6445550333322662121; 250 | let seed = u64::from_ne_bytes(query.clone_seed()); 251 | 252 | assert_eq!(seed, expected); 253 | }, 254 | ) 255 | .add_systems( 256 | PreUpdate, 257 | |query: Single, With>| { 258 | let expected = 2484862625678185386; 259 | let seed = u64::from_ne_bytes(query.clone_seed()); 260 | 261 | assert_eq!(seed, expected); 262 | }, 263 | ) 264 | .add_systems( 265 | Update, 266 | |mut commands: Commands, query: Single, With>| { 267 | commands.rng_entity(&query).reseed_from_source(); 268 | }, 269 | ) 270 | .add_systems( 271 | PostUpdate, 272 | |query: Single<&RngSeed, With>| { 273 | let prev_expected = 6445550333322662121; 274 | let expected = 14968821102299026759; 275 | let seed = u64::from_ne_bytes(query.clone_seed()); 276 | 277 | assert_ne!(seed, prev_expected); 278 | assert_eq!(seed, expected); 279 | }, 280 | ); 281 | 282 | app.run(); 283 | } 284 | 285 | #[test] 286 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 287 | pub fn generic_observer_reseeding_children() { 288 | use bevy_app::prelude::{Last, PostUpdate, PreUpdate, Startup}; 289 | use bevy_ecs::prelude::{Component, With, Without}; 290 | use bevy_rand::{commands::RngCommandsExt, seed::RngSeed, traits::SeedSource}; 291 | 292 | let seed = [2u8; 8]; 293 | 294 | #[derive(Component)] 295 | struct Source; 296 | #[derive(Component, Clone, Copy)] 297 | struct Target; 298 | 299 | let mut app = App::new(); 300 | 301 | app.add_plugins(( 302 | EntropyPlugin::::with_seed(seed), 303 | EntropyRelationsPlugin::::default(), 304 | )) 305 | .add_systems(Startup, |mut global: GlobalRngEntity| { 306 | global.rng_commands().with_target_rngs([( 307 | Source, 308 | RngLinks::::spawn(( 309 | Spawn(Target), 310 | Spawn(Target), 311 | Spawn(Target), 312 | Spawn(Target), 313 | Spawn(Target), 314 | )), 315 | )]); 316 | }) 317 | .add_systems( 318 | PreUpdate, 319 | |query: Query<&RngSeed, (With, Without)>| { 320 | let expected = [ 321 | 6445550333322662121u64, 322 | 14968821102299026759, 323 | 12617564484450995185, 324 | 908888629357954483, 325 | 6128439264405451235, 326 | ]; 327 | let seeds = query.iter().map(RngSeed::::clone_seed); 328 | 329 | assert_eq!(seeds.size_hint().0, 5); 330 | 331 | expected 332 | .into_iter() 333 | .zip(seeds.map(u64::from_ne_bytes)) 334 | .for_each(|(expected, actual)| { 335 | assert_eq!(expected, actual, "Expected output to match") 336 | }); 337 | }, 338 | ) 339 | .add_systems( 340 | PreUpdate, 341 | |query: Single<&RngSeed, With>| { 342 | let expected = 2484862625678185386u64; 343 | let seeds = u64::from_ne_bytes(query.clone_seed()); 344 | 345 | assert_eq!(expected, seeds, "Expected seeds to match"); 346 | }, 347 | ) 348 | .add_systems( 349 | Update, 350 | |mut commands: Commands, query: Query, With>| { 351 | for entity in &query { 352 | commands.rng_entity(&entity).reseed_linked(); 353 | } 354 | }, 355 | ) 356 | .add_systems( 357 | PostUpdate, 358 | |query: Query<&RngSeed, (With, Without)>| { 359 | let prev_expected = [ 360 | 6445550333322662121u64, 361 | 14968821102299026759, 362 | 12617564484450995185, 363 | 908888629357954483, 364 | 6128439264405451235, 365 | ]; 366 | 367 | let expected = [ 368 | 13007546668837876556u64, 369 | 11167966742313596632, 370 | 6059854582339877554, 371 | 16378674538987011914, 372 | 14627163487140195445, 373 | ]; 374 | 375 | let actual = query 376 | .iter() 377 | .map(RngSeed::::clone_seed) 378 | .map(u64::from_ne_bytes); 379 | 380 | assert_eq!(actual.size_hint().0, 5); 381 | 382 | prev_expected 383 | .into_iter() 384 | .zip(expected) 385 | .zip(actual) 386 | .for_each(|((previous, expected), actual)| { 387 | // Must not equal the previous seeds. 388 | assert_ne!( 389 | previous, actual, 390 | "Expected output not to match previous output" 391 | ); 392 | // Should equal the expected updated seeds. 393 | assert_eq!(expected, actual, "Expected output to be updated") 394 | }); 395 | }, 396 | ) 397 | .add_systems( 398 | Last, 399 | |source: Query<&RngSeed, With>, 400 | children: Query<&RngSeed, (Without, Without)>| { 401 | // Check we have the correct amount of allocated RNG entities 402 | assert_eq!( 403 | source.iter().size_hint().0, 404 | 1, 405 | "Only one SOURCE should exist" 406 | ); 407 | assert_eq!( 408 | children.iter().size_hint().0, 409 | 5, 410 | "Only 5 TARGET should exist" 411 | ); 412 | }, 413 | ); 414 | 415 | app.run(); 416 | } 417 | -------------------------------------------------------------------------------- /tests/suite.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::type_complexity)] 2 | #[cfg(feature = "compat")] 3 | #[path = "integration/bevy_math.rs"] 4 | pub mod bevy_math; 5 | #[path = "integration/determinism.rs"] 6 | pub mod determinism; 7 | #[path = "integration/extension.rs"] 8 | pub mod extension; 9 | #[path = "integration/reseeding.rs"] 10 | pub mod reseeding; 11 | 12 | #[cfg(target_arch = "wasm32")] 13 | wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); 14 | -------------------------------------------------------------------------------- /tutorial/00-overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Games often use randomness as a core mechanic. For example, card games generate a random deck for each game and killing monsters in an RPG often rewards players with a random item. While randomness makes games more interesting and increases replayability, it also makes games harder to test and prevents advanced techniques such as [deterministic lockstep](https://gafferongames.com/post/deterministic_lockstep/). 4 | 5 | Let's pretend you are creating a poker game where a human player can play against the computer. The computer's poker logic is very simple: when the computer has a good hand, it bets all of its money. To make sure the behavior works, you write a test to first check the computer's hand and if it is good confirm that all its money is bet. If the test passes does it ensure the computer behaves as intended? Sadly, no. 6 | 7 | Because the deck is randomly shuffled for each game (without doing so the player would already know the card order from the previous game), it is not guaranteed that the computer player gets a good hand and thus the betting logic goes unchecked. While there are ways around this (a fake deck that is not shuffled, running the test many times to increase confidence, breaking the logic into units and testing those) it would be very helpful to have randomness as well as a way to make it _less_ random. 8 | 9 | Luckily, when a computer needs a random number it doesn't use real randomness and instead uses a [pseudorandom number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator). Popular Rust libraries containing pseudorandom number generators are [`rand`](https://crates.io/crates/rand) and [`fastrand`](https://crates.io/crates/fastrand). 10 | 11 | Pseudorandom number generators require a source of [entropy](https://en.wikipedia.org/wiki/Entropy) called a [random seed](https://en.wikipedia.org/wiki/Random_seed). The random seed is used as input to generate numbers that _appear_ random but are instead in a specific and deterministic order. For the same random seed, a pseudorandom number generator always returns the same numbers in the same order. 12 | 13 | For example, let's say you seed a pseudorandom number generator with `1234`. You then ask for a random number between `10` and `99` and the pseudorandom number generator returns `12`. If you run the program again with the same seed (`1234`) and ask for another random number between `1` and `99`, you will again get `12`. If you then change the seed to `4567` and run the program, more than likely the result will not be `12` and will instead be a different number. If you run the program again with the `4567` seed, you should see the same number from the previous `4567`-seeded 14 | 15 | There are many types of pseudorandom number generators each with their own strengths and weaknesses. Because of this, Bevy does not include a pseudorandom number generator. Instead, the `bevy_rand` plugin includes a source of entropy to use as a random seed for your chosen pseudorandom number generator. 16 | 17 | Note that Bevy currently has [other sources of non-determinism](https://github.com/bevyengine/bevy/discussions/2480) unrelated to pseudorandom number generators. 18 | 19 | # Why and when to use Bevy_Rand? 20 | 21 | So you need to use some randomness in your game/application. In a lot of very simple cases where there is no requirement for portability (needing to run on many different platforms) and/or no need for determinism (being able to run with predetermined algorithm & seeds/states), you can simple use `rand` or `fastrand` directly. Maybe your case is just to quickly randomise some instantiation of entities/components, and there's not much need for anything more than that. For these simple cases, `bevy_rand` is a bit overkill as a solution. However, the moment you begin to care/need determinism and portability, then it makes sense to use `bevy_rand`. 22 | 23 | ## Portability 24 | 25 | This is the first concern of `bevy_rand`. The standard `StdRng`/`SmallRng` types from `rand` _are not portable_, meaning they are not expected to remain the same algorithm between different versions of the `rand` crate, or even the same PRNG algorithm between platforms. This is the case for `SmallRng`, which utilises different versions of the Xoshiro algorithm depending on whether you are on a 32-bit platform or 64-bit platform. 26 | 27 | The `rand` crate itself notes and states that if users are concerned about the portability of their PRNGs, then they should be using the algorithm crates directly instead, so pulling in `rand_chacha` or `rand_xoshiro`. A non-portable PRNG means you'll potentially be dealing with different randomness behaviour between your desktop version of your application and web version. Portability resolves this issue by forcing the PRNG algorithm to always be the same, no matter the platform or configuration. 28 | 29 | ## Determinism 30 | 31 | This is the second and most important concern of `bevy_rand`. `StdRng`/`SmallRng` types from `rand` might be _seedable_, but the lack of portability means they are _not deterministic_. The algorithm changing either from different versions of the `rand` crate or being on a different platform is _not deterministic_ behaviour. This property is highly important when it comes to ensuring correct behaviour across all versions of your game on whatever platform. But why would one consider determinism an important property? 32 | 33 | ### Testing 34 | 35 | Being able to "know" that your tests will always output with a set value despite dealing with randomness will allow you to detect changes in how your code is dealing with RNG. It might have an adverse effect later down the line, such as breaking stability assurances from serialized game states. Being able to track how RNG is handled by your game code can increase and mitigate any breaking changes from occuring. 36 | 37 | ### Saving/Loading/Synchronising States 38 | 39 | If you want to have a replay system that can show all the player's moves, including attacks that had critical hits, activated procs, or other randomised effects in the exact order they played out? That requires being able to go through the game's recorded state and rerun through it. If you have a deterministic simulation, just replaying the player's inputs would lead to the simulation result being the exact same as when it played out. By having the PRNG sources being deterministic as well in not just the setup (the seed), but in its usage, then it would still yield the deterministic output desired. 40 | 41 | This principle can be extended to saving/loading game states to disk, not needing to worry about reinitialising the states of your PRNGs as their states can just be serialised and deserialised. Or synchronising clients with the same PRNG states to ensure their simulations are in agreement with each other. 42 | 43 | ### Parallelising RNG usage 44 | 45 | Using a PRNG global source not only makes it very difficult to maintain determinism, but it also makes it impossible to parallelise systems that access it. For larger games with lots of systems, this could become a bottleneck and cause your game to underutilise modern hardware, given the proliferation of CPUs with large core/thread counts. `bevy_rand` introduces concepts and strategies that can be used to avoid needing to use a global source, which then not only potentially allows for parallelising systems, but also parallelising query iterations. One could parallelise RNG usage/access with thread local sources, but you then *lose* determinism as the systems and queries are no longer guaranteed to run on the same thread each time. 46 | 47 | ### And much more 48 | 49 | `bevy_rand` might introduce some extra complexity with handling PRNGs within Bevy, but it is primarily for unblocking purposes that require that complexity. The abstractions necessary to do all this are not that difficult to understand or use, but do require thinking away from "global" sources and more towards "per entity" sources. 50 | -------------------------------------------------------------------------------- /tutorial/01-choosing-prng.md: -------------------------------------------------------------------------------- 1 | # Selecting and using PRNG Algorithms 2 | 3 | If you need a TL;DR of this section, then select `wyrand` for the fastest PRNG with good enough entropy, or select `rand_chacha` for the best quality entropy at the expense of speed (with `ChaCha8Rng` being the fastest of the three available ones, with `ChaCha20Rng` being the most secure). If you still do not know which one to pick, pick `wyrand`, because for most purposes in games, you do not need cryptographically secure levels of entropy. 4 | 5 | ## Choosing a PRNG 6 | 7 | All supported PRNGs and compatible structs are provided by the `bevy_prng` crate. Simply activate the relevant features in `bevy_rand`/`bevy_prng` to pull in the PRNG algorithm you want to use, and then import them like so: 8 | 9 | ```toml 10 | bevy_rand = { version = "0.11", features = ["rand_chacha", "wyrand"] } 11 | ``` 12 | ```rust ignore 13 | use bevy::prelude::*; 14 | use bevy_rand::prelude::{ChaCha8Rng, WyRand}; 15 | ``` 16 | or 17 | ```toml 18 | bevy_rand = "0.11" 19 | bevy_prng = { version = "0.11", features = ["rand_chacha", "wyrand"] } 20 | ``` 21 | ```rust ignore 22 | use bevy::prelude::*; 23 | use bevy_rand::prelude::*; 24 | use bevy_prng::{ChaCha8Rng, WyRand}; 25 | ``` 26 | 27 | When you've selected and imported the PRNG algorithm you want to use, you then need to enable it by adding the `EntropyPlugin` to your app. This then makes it available for use with `GlobalEntropy` and `Entropy`, as well as registering the types for reflection. 28 | 29 | ```rust 30 | use bevy_ecs::prelude::*; 31 | use bevy_app::prelude::*; 32 | use bevy_prng::WyRand; 33 | use bevy_rand::prelude::EntropyPlugin; 34 | use rand_core::RngCore; 35 | 36 | fn main() { 37 | App::new() 38 | .add_plugins(EntropyPlugin::::default()) 39 | .run(); 40 | } 41 | ``` 42 | 43 | By default, the plugin will instantiate a global `Entropy` entity (accessible via `GlobalEntropy`) with a random seed from OS sources. If you want to initialise the plugin and `GlobalEntropy` with a set seed or from a different source, use [`crate::prelude::EntropyPlugin::with_seed`] instead. 44 | 45 | ```rust 46 | use bevy_ecs::prelude::*; 47 | use bevy_app::prelude::*; 48 | use bevy_prng::WyRand; 49 | use bevy_rand::prelude::EntropyPlugin; 50 | use rand_core::RngCore; 51 | 52 | fn main() { 53 | let seed: u64 = 234; // How you source this is upto you. 54 | 55 | App::new() 56 | .add_plugins(EntropyPlugin::::with_seed(seed.to_ne_bytes())) 57 | .run(); 58 | } 59 | ``` 60 | 61 | ## Supported Algorithms 62 | 63 | The current set of PRNG algorithms that are supported out of the box in `bevy_prng` are as follows: 64 | 65 | - `wyrand`: This provides newtyped `WyRand` from `wyrand`, the same algorithm in use within `fastrand`/`turborand`. 66 | - `rand_xoshiro`: This provides newtyped `Xoshiro*` structs from `rand_xoshiro`. It also exports a remote-reflected version of `Seed512` so to allow setting up `Xoshiro512StarStar` and so forth. 67 | - `rand_pcg`: This provides newtyped `Pcg*` structs from `rand_pcg`. 68 | - `rand_chacha`: This provides newtyped `ChaCha*Rng` structs, for those that want/need to use a CSPRNG level source. 69 | 70 | ## `bevy_prng` and newtypes 71 | 72 | Trying to use PRNGs directly as resources/components from the `rand_*` crates is not possible without newtyping, and better to use `bevy_prng` instead of writing your own newtypes. Types from `rand` like `StdRng` and `SmallRng` are not encouraged to be used or newtyped as they are not **portable**, nor are they guaranteed to be stable. `SmallRng` is not even the same algorithm if used on 32-bit platforms versus 64-bit platforms. This makes them unsuitable for purposes of reflected/stable/portable types needed for deterministic PRNG. Likely, you'd be using `StdRng`/`SmallRng` directly without integration into Bevy's ECS. This is why the algorithm crates are used directly by `bevy_prng`, as this is the recommendation from `rand` when stability and portability is important. 73 | 74 | ## Factors for selecting a PRNG algorithm 75 | 76 | As a whole, which algorithm should be used/selected is dependent on a range of factors. Cryptographically Secure PRNGs (CSPRNGs) produce very hard to predict output (very high quality entropy), but in general are slow. The ChaCha algorithm can be sped up by using versions with less rounds (iterations of the algorithm), but this in turn reduces the quality of the output (making it easier to predict), or by compiling with CPU features enabled such as SIMD (AVX2 support in particular). However, `ChaCha8Rng` is still far stronger than what is feasible to be attacked, and is considerably faster as a source of entropy than the full `ChaCha20Rng`. `rand` uses `ChaCha12Rng` as a balance between security/quality of output and speed for its `StdRng`. CSPRNGs are important for cases when you _really_ don't want your output to be predictable and you need that extra level of assurance, such as doing any cryptography/authentication/security tasks. Do note however, `rand` is not intended to be a cryptography crate, nor used for cryptography purposes, and that should be delegated towards crates designed for that purpose. 77 | 78 | If that extra level of randomness is not necessary (which will be most cases within a game), but there is still need for extra speed while maintaining good enough randomness, other PRNG algorithms exist for this purpose. These algorithms still try to output as high quality entropy as possible, but the level of entropy is not enough for cryptographic purposes. These algorithms should **never be used in situations that demand security**. Algorithms like `WyRand` and `Xoshiro256StarStar` are tuned for maximum throughput, while still possessing _good enough_ entropy for use as a source of randomness for non-security purposes. It still matters that the output is not predictable, but not to the same extent as CSPRNGs are required to be. PRNGs like `WyRand` also have small state sizes, which makes them take less memory per instance compared to CSPRNGs like `ChaCha8Rng`. 79 | -------------------------------------------------------------------------------- /tutorial/02-basic-usage.md: -------------------------------------------------------------------------------- 1 | # Basic Usage with `GlobalEntropy` 2 | 3 | At the simplest case, using `GlobalEntropy` directly for all random number generation, though this does limit how well systems using `GlobalEntropy` can be parallelised. This is because `GlobalEntropy` is a query to access a single entity with a mutable reference to the `Entropy` component. All systems that access `GlobalEntropy` will run serially to each other. 4 | 5 | ```rust 6 | use bevy_prng::ChaCha8Rng; 7 | use bevy_rand::prelude::GlobalEntropy; 8 | use rand_core::RngCore; 9 | 10 | fn print_random_value(mut rng: GlobalEntropy) { 11 | println!("Random value: {}", rng.next_u32()); 12 | } 13 | ``` 14 | 15 | In addition, the `rand` crate can be optionally pulled in and used with `bevy_rand`, benefitting from the full suite of methods and utilities. This allows full compatibility with all the distribution utilities within `rand` and also with `bevy_math`. 16 | 17 | ```rust ignore 18 | use bevy_math::{ShapeSample, primitives::Circle}; 19 | use bevy_prng::ChaCha8Rng; 20 | use bevy_rand::prelude::GlobalEntropy; 21 | use rand::Rng; 22 | 23 | fn print_random_value(mut rng: GlobalEntropy) { 24 | println!("Random u128 value: {}", rng.random::()); 25 | } 26 | 27 | fn sample_from_circle(mut rng: GlobalEntropy) { 28 | let circle = Circle::new(42.0); 29 | 30 | let boundary = circle.sample_boundary(rng.as_mut()); 31 | let interior = circle.sample_interior(rng.as_mut()); 32 | 33 | println!("Sampled values from Circle: {boundary:?}, {interior:?}"); 34 | } 35 | ``` 36 | 37 | ## Determinism when using `GlobalEntropy` 38 | 39 | When using `GlobalEntropy`, the way to ensure deterministic output/usage with the single entity is in the following ways: 40 | 41 | - One time or non-looping accesses when generating random numbers. 42 | - Iterating over set loops/ranges. 43 | - Systems constrained with clear priorities and ordering so there are no ambiguities. 44 | 45 | An example of a guaranteed deterministic system is perhaps spawning new entities with a randomised component value: 46 | 47 | ```rust 48 | use bevy_ecs::prelude::*; 49 | use bevy_prng::WyRand; 50 | use bevy_rand::prelude::GlobalEntropy; 51 | use rand_core::RngCore; 52 | 53 | #[derive(Component)] 54 | struct Npc; 55 | 56 | #[derive(Component)] 57 | struct Stat(u32); 58 | 59 | fn spawn_randomised_npcs(mut commands: Commands, mut rng: GlobalEntropy) { 60 | for _ in 0..10 { 61 | commands.spawn(( 62 | Npc, 63 | Stat(rng.next_u32()) 64 | )); 65 | } 66 | } 67 | ``` 68 | 69 | The above system will iterate a set number of times, and will yield 10 randomis `u32` values, leaving the PRNG in a determined state. Any system that then accesses `GlobalEntropy` afterwards will always yield a predetermined value if the PRNG was given a set seed. 70 | 71 | However, iterating over queries will **not** yield deterministic output, as queries are not guaranteed to iterate over collected entities in the same order every time the system is ran. Therefore, the below example will not have deterministic output. 72 | 73 | ```rust 74 | use bevy_ecs::prelude::*; 75 | use bevy_prng::WyRand; 76 | use bevy_rand::prelude::GlobalEntropy; 77 | use rand_core::RngCore; 78 | 79 | #[derive(Component)] 80 | struct Npc; 81 | 82 | #[derive(Component)] 83 | struct Stat(u32); 84 | 85 | fn randomise_npc_stat(mut rng: GlobalEntropy, mut q_npc: Query<&mut Stat, With>) { 86 | for mut stat in q_npc.iter_mut() { 87 | stat.0 = rng.next_u32(); 88 | } 89 | } 90 | ``` 91 | 92 | But how can we achieve determinism in cases of where we want to randomise values within a query iteration? Well, by not using `GlobalEntropy` but `Entropy` instead, moving the RNG source from a single global entity to the entities you want to provide entropy to themselves. The next section will cover their usage. 93 | -------------------------------------------------------------------------------- /tutorial/03-components-forking.md: -------------------------------------------------------------------------------- 1 | # Entities as RNG sources with `Entropy` 2 | 3 | In order to move beyond the restrictions placed by `GlobalEntropy` and achieve determinism *with parallelism*, where the RNG source lives has to go from a global source to one owned by the entities themselves. `Entropy` enables us to attach a PRNG to any given entity, and thus sidesteps not only forcing systems to run serially to each other, but also avoids the problem of queries not being stable in ordering. In fact, as ordering is no longer an issue, parallel iteration of queries is made possible as we avoid borrowing issues if each entity we queried owns its own RNG source. 4 | 5 | ```rust 6 | use bevy_ecs::prelude::*; 7 | use bevy_prng::WyRand; 8 | use bevy_rand::prelude::Entropy; 9 | 10 | #[derive(Component)] 11 | struct Source; 12 | 13 | fn setup_source(mut commands: Commands) { 14 | commands 15 | .spawn(( 16 | Source, 17 | Entropy::::default(), 18 | )); 19 | } 20 | ``` 21 | 22 | In the above example, we are creating an entity with a `Source` marker component and attaching an `Entropy` to it with the `WyRand` algorithm and a randomised seed. To then access this source, we simply query `Query<&mut Entropy, With>`. In this case, we are creating a single entity with an RNG source, but there's no reason why many more can't have an RNG source attached to them. 23 | 24 | ```rust 25 | use bevy_ecs::prelude::*; 26 | use bevy_prng::WyRand; 27 | use bevy_rand::prelude::Entropy; 28 | 29 | #[derive(Component)] 30 | struct Npc; 31 | 32 | fn setup_source(mut commands: Commands) { 33 | for _ in 0..10 { 34 | commands 35 | .spawn(( 36 | Npc, 37 | Entropy::::default(), 38 | )); 39 | } 40 | } 41 | ``` 42 | 43 | `GlobalEntropy` is basically the same thing! It's just an `Entity` with an `Entropy` component and `RngSeed`, combined with a `Global` marker component. `GlobalEntropy` itself is not a type, but an alias for a query: `Query<&mut Entropy, With>`. 44 | 45 | We can also instantiate these components with set seeds, but there's then the danger that with all of them having the same seed, they'll output the same random numbers. But we want determinism without being easy to predict across many, many entities. How would one achieve this? By forking. 46 | 47 | ## Forking new sources from existing ones 48 | 49 | Forking is the process of generating a new seed from an RNG source and creating a new RNG instance with it. If cloning creates a new instance with the same state from the old, forking creates a new instance with a new state, advancing the old instance's state in the process (as we used it to generate a new seed). 50 | 51 | Because PRNG algorithms are deterministic, forking is a deterministic process, and it allows us to have one seed state create many "random" states while being hard to predict. `bevy_rand` makes it super easy to fork new `Entropy`s, allowing you to source new RNGs from `GlobalEntropy` or even other `Entropy`s! 52 | 53 | ```rust 54 | use bevy_ecs::prelude::*; 55 | use bevy_prng::ChaCha8Rng; 56 | use bevy_rand::prelude::{Entropy, GlobalEntropy, ForkableRng}; 57 | 58 | #[derive(Component)] 59 | struct Source; 60 | 61 | fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 62 | commands 63 | .spawn(( 64 | Source, 65 | global.fork_rng(), // This will yield an `Entropy` 66 | )); 67 | } 68 | ``` 69 | 70 | We can even fork to different PRNG algorithms. 71 | 72 | ```rust 73 | use bevy_ecs::prelude::*; 74 | use bevy_prng::{ChaCha8Rng, WyRand}; 75 | use bevy_rand::prelude::{Entropy, GlobalEntropy, ForkableAsRng}; 76 | 77 | #[derive(Component)] 78 | struct Source; 79 | 80 | fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 81 | commands 82 | .spawn(( 83 | Source, 84 | global.fork_as::(), // This will yield an `Entropy` 85 | )); 86 | } 87 | ``` 88 | 89 | So we created a `Source` entity with an RNG source, let's use it to spawn more entities with RNG sources! 90 | 91 | ```rust 92 | use bevy_ecs::prelude::*; 93 | use bevy_prng::WyRand; 94 | use bevy_rand::prelude::{Entropy, ForkableRng}; 95 | 96 | #[derive(Component)] 97 | struct Npc; 98 | 99 | #[derive(Component)] 100 | struct Source; 101 | 102 | fn setup_npc_from_source( 103 | mut commands: Commands, 104 | mut q_source: Single<&mut Entropy, (With, Without)>, 105 | ) { 106 | for _ in 0..10 { 107 | commands 108 | .spawn(( 109 | Npc, 110 | q_source.fork_rng() // This will yield a new `Entropy` 111 | )); 112 | } 113 | } 114 | ``` 115 | 116 | Now that we have our `Npc` entities attached with RNG sources, when we query them, we can make use of their own sources when generating new random numbers from them. 117 | 118 | ```rust ignore 119 | fn randomise_npc_stat(mut q_npc: Query<(&mut Stat, &mut Entropy), With>) { 120 | for (mut stat, mut rng) in q_npc.iter_mut() { 121 | stat.0 = rng.next_u32(); 122 | } 123 | } 124 | ``` 125 | 126 | This way, no matter what order the query iterates, we can be assured that the resulting output is always deterministic. Other systems that access different entities with RNG sources that don't overlap with `Npc` entity systems will be able to run in parallel, and iterating the queries themselves can also be done in parallel with `.par_iter()`. We've ensured that each *access* is deterministic and owned to the entity itself. 127 | 128 | As a final note: for both `GlobalEntropy` and `Entropy`s, one can fork the inner PRNG instance to use directly or pass into methods via `fork_inner()`. 129 | 130 | ## Pitfalls when Querying 131 | 132 | In general, never do a `Query<&mut Entropy>` without any query filters. 133 | 134 | In basic usages, there's only *one* entity, the `Global` entity for the enabled RNG algorithm. The above query will yield the `Global` entity, same as using `GlobalEntropy` query helper. However, if you've spawned more than one source, the above query will yield *all* `Entropy` entities, global and non-global ones included. The ordering is also not guaranteed, so the first result out of that query is not guaranteed to be the global entity. 135 | 136 | Therefore, always use something like `Single` to enforce access to a single source such as `Single<&mut Entropy, With>`, or use query helpers like `GlobalEntropy` to access global sources, or use a suitable filter for a marker component to filter out other sources from the ones you are interested in: `Query<&mut Entropy, With>`. 137 | -------------------------------------------------------------------------------- /tutorial/04-seeding.md: -------------------------------------------------------------------------------- 1 | # Seeding 2 | 3 | So, why cover seeding at this point for its own chapter? Firstly, seeding can be an issue to get right. Nearly all RNGs are actually *pseudo*random, and are in fact just clever algorithms that output sequences of numbers that *look* random. Unless you are pulling your random numbers from a hardware source which samples from things like thermal noise, etc, your RNG is likely to be a *pseudorandom* generator. Very few sources are *true* random sources, and usually, such sources are actually quite *slow*, due to the need to collect enough entropy. 4 | 5 | `bevy_rand` only really deals with PRNGs, and PRNGs *need* a seed value when initialised in order for the algorithm to do its thing. But why not just use `0` as a default? Because then the resulting sequence will always be the same. It'll be a *random* looking sequence, but every time you run the program, that sequence will be replayed. Because *pseudorandom* generators are deterministic. 6 | 7 | This is a good property to have though! It means the algorithm is *testable*, and you can also test with it. And if the output is the same across different platforms, then it is *portable*. Even better! But it does mean you can't really use a *default* value. If you are using an RNG, you don't really *want* the same sequence every time you run your program. It would be too predictable, and in the context of a game, that can have some not so great consequences if players notice this. 8 | 9 | ## Where to get seeds? 10 | 11 | `bevy_rand` provides two ways to give an `Entropy` component a seed: 12 | 13 | 1. Default: Pulling from a thread-local or OS source (Random) 14 | 2. Providing a set seed (Deterministic) 15 | 16 | Wait, the first option is a default? It's true that library crates like `rand_chacha` don't implement their algorithms with defaults, but that's because they can't make assumptions about what sources are available by default and which platforms the algorithm will be used on. `bevy_rand` *can* make some assumptions however, because: 17 | 18 | * `bevy_rand` is being used in the context of making games/applications in `bevy`, so we are assuming this will be used on platforms with the capability to support/run `bevy` apps (std or no-std). 19 | * These platforms will have the ability to provide OS/hardware sources or allow for user-space sources. 20 | 21 | In the rare occasion these assumptions cannot be upheld, there's still an escape hatch for this, but it involves getting stuck into `getrandom`. For those cases, I defer to the [getrandom documentation](https://docs.rs/getrandom/0.2.15/getrandom/macro.register_custom_getrandom.html) on how to enable support for these particular platforms. 22 | 23 | Otherwise for most users, `bevy_rand` makes use of defaults to pull in random seeds from platform/user-space sources, when you aren't concerned about what seed but need it to be random enough that there's no chance that it can be predicted. As long as the above infrastructure is in place, it'll do this automatically. Once you have at least *one* random seed, it also becomes possible to use one source to generate new seeds for more sources, allowing for a *deterministic* distribution of seeds. 24 | 25 | However, sometimes you want to make sure things are indeed random, or that what was distributed was deterministic, etc. That's where `RngSeed` component comes in. 26 | 27 | ## Knowing what seed you got 28 | 29 | In the `rand` ecosystem, it has been decided for a long while that for security reasons, it should not be possible to observe the internal states of PRNGs via `Debug` or `Display`. There are good reasons for this "black box" approach, but in the context of game dev and needing to iterate and be able to observe the state of your game, it then makes debugging a very difficult prospect. You'd have to constantly serialise the RNG in order to gain insight to its internal state and that adds overhead. 30 | 31 | But for most purposes, you don't actually *need* to know its exact internal state. Given these are *pseudorandom* sources, it is enough to observe the *initial state*, aka the seed value. `RngSeed` provides the ability to store the initial seed in a way that is observable via reflection and `Debug`. This way, you can observe your source entities without needing to worry about constantly serialising your RNGs. 32 | 33 | `RngSeed` does more though. It also *instantiates* an `Entropy` component automatically with the provided seed value when it is inserted on an entity. For example: This means that instead of transmitting the serialised state of an RNG from a server to a client, you could just transmit the `RngSeed` component, and when it is instantiated on the client, it'll setup `Entropy` automatically. 34 | 35 | ```rust 36 | use bevy_ecs::prelude::*; 37 | use bevy_prng::WyRand; 38 | use bevy_rand::prelude::RngSeed; 39 | 40 | #[derive(Component)] 41 | struct Source; 42 | 43 | fn setup_source(mut commands: Commands) { 44 | commands 45 | .spawn(( 46 | Source, 47 | // This will yield a random `RngSeed` and then an `Entropy` 48 | // with the same random seed 49 | RngSeed::::default(), 50 | )); 51 | } 52 | ``` 53 | 54 | It also means all the previous examples about forking `Entropy` components can be directly adapted into forking *seeds* instead. 55 | 56 | ```rust 57 | use bevy_ecs::prelude::*; 58 | use bevy_prng::WyRand; 59 | use bevy_rand::prelude::{Entropy, GlobalEntropy, ForkableSeed}; 60 | 61 | #[derive(Component)] 62 | struct Source; 63 | 64 | fn setup_source(mut commands: Commands, mut global: GlobalEntropy) { 65 | commands 66 | .spawn(( 67 | Source, 68 | // This will yield a `RngSeed` and then an `Entropy` 69 | global.fork_seed(), 70 | )); 71 | } 72 | ``` 73 | 74 | The `SeedSource` trait provides the methods needed to get access to the wrapped seed value, though `RngSeed` does implement `Deref` as well. 75 | 76 | ## Reseeding 77 | 78 | In order to ensure that new seeds always proliferate into updating `Entropy` components, `RngSeed` is an *immutable* component. It does not allow obtaining mutable references to it, nor exposes mutable methods to modify the seed. That means the only way to update a source entity's seed is to reinsert a new `RngSeed`. By doing so, it also triggers the component's hooks to also reinsert a new `Entropy` with the updated seed. 79 | 80 | ```rust 81 | use bevy_ecs::prelude::*; 82 | use bevy_prng::WyRand; 83 | use bevy_rand::prelude::{RngSeed, SeedSource, GlobalRngEntity}; 84 | 85 | #[derive(Component)] 86 | struct Source; 87 | 88 | fn setup_source(mut global: GlobalRngEntity) { 89 | let new_seed = [42; 8]; // This seed has been chosen as random 90 | 91 | global.rng_commands().reseed(new_seed); 92 | } 93 | ``` 94 | 95 | Reinsertions do not invoke archetype moves, so this will not cause any extra overhead. There's also `RngEntity` query data that encapsulates `Entity` and `RngSeed` together, which then allows for more seamless usage with `RngEntityCommands`. 96 | 97 | ```rust 98 | use bevy_ecs::prelude::*; 99 | use bevy_prng::WyRand; 100 | use bevy_rand::prelude::{Entropy, GlobalEntropy, RngEntity, RngCommandsExt}; 101 | use rand::Rng; 102 | 103 | #[derive(Component)] 104 | struct Source; 105 | 106 | fn reseed_sources(mut commands: Commands, q_sources: Query, With>, mut global: GlobalEntropy) { 107 | for rng_entity in q_sources.iter() { 108 | commands.rng_entity(&rng_entity).reseed(global.random()); 109 | } 110 | } 111 | 112 | ``` 113 | 114 | ## Pitfalls when Querying 115 | 116 | In general, never do a `Query<&RngSeed>` or `Query>` without any query filters. 117 | 118 | In basic usages, there's only *one* entity, the `Global` entity for the enabled RNG algorithm. The above query will yield the `Global` entity, same as using `GlobalRngEntity` system param. However, if you've spawned more than one source, the above query will yield *all* `RngSeed` entities, global and non-global ones included. The ordering is also not guaranteed, so the first result out of that query is not guaranteed to be the global entity. 119 | 120 | Therefore, always use something like `Single` to enforce access to a single source such as `Single<&RngSeed, With>`, or use query helpers like `GlobalRngEntity` to access global sources, or use a suitable filter for a marker component to filter out other sources from the ones you are interested in: `Query, With>`. 121 | -------------------------------------------------------------------------------- /tutorial/05-observer-driven-reseeding.md: -------------------------------------------------------------------------------- 1 | # Observer-driven and Command-based Reseeding 2 | 3 | Managing the seeding of related RNGs manually can be complex and also boilerplate-y, so `bevy_rand` provides a commands API powered by observers and bevy relations to make it much more easy to setup and maintain. This way, pushing a new seed to a single "source" RNG will then automatically push out new seeds to all linked "target" RNGs. It can also be set up for seeding between different kinds of PRNG, but it does require the addition of an extra plugin in order to facilitate this particular case. 4 | 5 | The nature of the relations are strictly either One to One or One to Many. Many to Many relations are **not** supported, as it does not make sense for PRNG to have multiple source PRNGs. 6 | 7 | ```rust 8 | use bevy_app::prelude::*; 9 | use bevy_prng::{ChaCha8Rng, WyRand}; 10 | use bevy_rand::prelude::{EntropyPlugin, EntropyRelationsPlugin}; 11 | 12 | fn main() { 13 | App::new() 14 | .add_plugins(( 15 | // First initialise the RNGs 16 | EntropyPlugin::::default(), 17 | EntropyPlugin::::default(), 18 | // This initialises observers for WyRand -> WyRand seeding relations 19 | EntropyRelationsPlugin::::default(), 20 | // This initialises observers for ChaCha8Rng -> WyRand seeding relations 21 | EntropyRelationsPlugin::::default(), 22 | )) 23 | .run(); 24 | } 25 | ``` 26 | 27 | Once the plugins are initialised, various observer systems are ready to begin listening to various linking and reseeding events. Relations can exist between a global source and "local" sources, or between other entity local sources. So for example, a single `Global` source can seed many `Player` entities with their own RNGs, and to reseed all `Player` entities, you just need to push a new seed to the global source, or tell the global source to reseed all its linked RNGs. 28 | 29 | ```rust 30 | use bevy_ecs::prelude::*; 31 | use bevy_prng::WyRand; 32 | use bevy_rand::prelude::GlobalRngEntity; 33 | 34 | #[derive(Component)] 35 | struct Target; 36 | 37 | fn link_and_seed_target_rngs_with_global(q_targets: Query>, mut global: GlobalRngEntity) { 38 | let targets = q_targets.iter().collect::>(); 39 | 40 | global.rng_commands().link_target_rngs(&targets).reseed_linked(); 41 | } 42 | ``` 43 | 44 | In the above example, we have created a relationship between the `GlobalRngEntity` source and all `Target` entities. The above system creates the relations and then emits a reseeding event, causing all `Target` entities to receive a new `RngSeed` component from the `Global` source. This in turn initialises an `Entropy` component on each `Target` entity with the received seed. 45 | 46 | If you want to spawn related entities directly, then you can! The example below will create three `Target` entities that are related to the `Global` source, and will be seeded automatically once spawned. 47 | 48 | ```rust 49 | use bevy_ecs::prelude::*; 50 | use bevy_prng::WyRand; 51 | use bevy_rand::prelude::GlobalRngEntity; 52 | 53 | #[derive(Component)] 54 | struct Target; 55 | 56 | fn link_and_seed_target_rngs_with_global(mut global: GlobalRngEntity) { 57 | global.rng_commands().with_target_rngs([Target, Target, Target]); 58 | } 59 | ``` 60 | 61 | The `GlobalRngEntity` is a special `SystemParam` that access the `Global` source `Entity` for a particular PRNG type. This then allows you to directly ask for an `RngEntityCommands` via the `rng_commands()` method. With this, you can link and seed your Source or Targets. 62 | 63 | Alternatively, one can provide a set seed to reseed all target entities with: 64 | 65 | ```rust 66 | use bevy_ecs::prelude::*; 67 | use bevy_prng::WyRand; 68 | use bevy_rand::prelude::*; 69 | 70 | #[derive(Component)] 71 | struct Target; 72 | 73 | fn intialise_rng_entities_with_set_seed(mut commands: Commands, mut q_targets: Query>) { 74 | let seed = u64::to_ne_bytes(42); 75 | 76 | for target in &q_targets { 77 | commands.entity(target).rng::().reseed(seed); 78 | } 79 | } 80 | ``` 81 | 82 | The commands API takes an `EntityCommands` and extends it to provide an `rng()` method that takes a generic parameter to define which PRNG you want to initialise/use, and then provides methods to trigger events for seeding either the entity itself, or creating relations between other entities for seeding. 83 | 84 | For entities that already have `RngSeed` attached to them, you can make use of `RngEntity` to query them. `Commands` also has a `rng()` method provided that takes a `&RngEntity` to give a `RngEntityCommands` without needing to explicitly provide the generics parameter to yield the correct PRNG target. 85 | 86 | These command APIs are designed to alleviate or reduce some of the generic typing around the Rng components, so to make it less error prone and more robust that you are targetting the correct PRNG type, and also to make it easier on querying and managing more complex relations of dependencies between RNGs for seeding. 87 | 88 | Once the relations are created, it becomes easy to pull new seeds from sources/global using the commands API: 89 | 90 | ```rust 91 | use bevy_ecs::prelude::*; 92 | use bevy_prng::WyRand; 93 | use bevy_rand::prelude::{RngEntity, RngCommandsExt}; 94 | 95 | #[derive(Component)] 96 | struct Source; 97 | 98 | #[derive(Component)] 99 | struct Target; 100 | 101 | fn pull_seeds_from_source(mut commands: Commands, q_targets: Query, With>) { 102 | for entity in q_targets { 103 | commands.rng_entity(&entity).reseed_from_source(); 104 | } 105 | } 106 | ``` 107 | 108 | Of course, one _can_ also make use of the observer events directly, though it does require more typing to be involved. An example below: 109 | 110 | ```rust 111 | use bevy_ecs::prelude::*; 112 | use bevy_prng::WyRand; 113 | use bevy_rand::prelude::{SeedFromGlobal, RngLinks}; 114 | 115 | #[derive(Component)] 116 | struct Source; 117 | 118 | #[derive(Component, Clone, Copy)] 119 | struct Target; 120 | 121 | fn initial_setup(mut commands: Commands) { 122 | // Create the source entity with its related target entities and get the Entity id. 123 | // You can also make use of the related! macro for this instead. 124 | let source = commands 125 | .spawn((Source, RngLinks::::spawn(( 126 | Spawn(Target), 127 | Spawn(Target), 128 | Spawn(Target) 129 | )))) 130 | .id(); 131 | 132 | // Initialise the Source entity to be an RNG source and then seed all its 133 | // linked entities. 134 | commands.trigger_targets(SeedFromGlobal::::default(), source); 135 | } 136 | ``` 137 | 138 | Once the link has been created, child entities can also pull a new seed from its parent source. So if you want to reseed *one* entity from its parent source, but not all of the entities that have the same source, you can use the `SeedFromSource` observer event to achieve this. 139 | 140 | ```rust 141 | use bevy_ecs::prelude::*; 142 | use bevy_prng::WyRand; 143 | use bevy_rand::observers::SeedFromSource; 144 | 145 | #[derive(Component)] 146 | struct Source; 147 | 148 | #[derive(Component, Clone, Copy)] 149 | struct Target; 150 | 151 | fn pull_seed_from_parent(mut commands: Commands, mut q_targets: Query>) { 152 | for target in &q_targets { 153 | commands.trigger_targets(SeedFromSource::::default(), target); 154 | } 155 | } 156 | ``` 157 | 158 | ## Note about relations between PRNG types 159 | 160 | As covered in Chapter One: "Selecting and using PRNG Algorithms", when creating relationship between different PRNG types, do not seed a stronger PRNG from a weaker one. CSPRNGs like `ChaCha8` should be seeded by either other `ChaCha8` sources or "stronger" sources like `ChaCha12` or `ChaCha20`, never from ones like `WyRand` or `Xoshiro256StarStar`. Always go from stronger to weaker, or same to same, never from weaker to stronger. Doing so makes it easier to predict the seeded PRNG, and reduces the advantage of using a CSPRNG in the first place. 161 | 162 | So in summary: 163 | 164 | | Source types | Target types | | 165 | | ------------------------------------ | ------------------------------------ | ------- | 166 | | `ChaCha8` / `ChaCha12` / `ChaCha20` | `WyRand` / `Xoshiro256StarStar` | ✅ Good | 167 | | `ChaCha12` / `ChaCha20` | `ChaCha8` | ✅ Good | 168 | | `ChaCha8` | `ChaCha8` | ✅ Good | 169 | | `WyRand` | `WyRand` | ✅ Good | 170 | | `Wyrand` /`Xoshiro256StarStar` | `ChaCha8` / `ChaCha12` / `ChaCha20` | ❌ Bad | 171 | | `ChaCha8` | `ChaCha12` / `ChaCha20` | ❌ Bad | 172 | --------------------------------------------------------------------------------