#random #bevy #bevy-ecs #secure-random #game-engine #bevy-plugin #wyrand

bevy_turborand

A plugin to enable ECS optimised random number generation for the Bevy game engine

16 releases (9 breaking)

0.10.0 Nov 30, 2024
0.9.0 Jul 4, 2024
0.8.2 Feb 24, 2024
0.7.0 Nov 4, 2023
0.2.0 Jul 30, 2022

#334 in Game dev

Download history 66/week @ 2024-08-17 51/week @ 2024-08-24 67/week @ 2024-08-31 72/week @ 2024-09-07 70/week @ 2024-09-14 152/week @ 2024-09-21 175/week @ 2024-09-28 60/week @ 2024-10-05 88/week @ 2024-10-12 80/week @ 2024-10-19 61/week @ 2024-10-26 129/week @ 2024-11-02 49/week @ 2024-11-09 55/week @ 2024-11-16 99/week @ 2024-11-23 196/week @ 2024-11-30

436 downloads per month
Used in 3 crates

Apache-2.0 OR MIT

48KB
591 lines

bevy_turborand

CI License Cargo Documentation

Notice

For all intents and purposes, bevy_turborand will no longer receive new features or work, and is mostly on maintenance only mode. I will keep this crate up-to-date with bevy releases, but otherwise all new work and efforts is currently directed towards bevy_rand. Folks who wish to add more capability to this crate are free to submit PRs.

Summary

A plugin to enable random number generation for the Bevy game engine, built upon turborand. Implements ideas from Bevy's Deterministic RNG RFC.

turborand's internal implementation uses Wyrand, a simple and fast generator but not cryptographically secure, as well as ChaCha8, a cryptographically secure generator tuned to 8 rounds of the ChaCha algorithm for increased throughput without sacrificing too much security, as per the recommendations in the Too Much Crypto paper.

Example

use bevy::prelude::*;
use bevy_turborand::prelude::*;

#[derive(Debug, Component)]
struct Player;

fn setup_player(mut commands: Commands, mut global_rng: ResMut<GlobalRng>) {
    commands.spawn((
        Player,
        RngComponent::from(&mut global_rng)
    ));
}

fn do_damage(mut q_player: Query<&mut RngComponent, With<Player>>) {
    let mut rng = q_player.single_mut();

    println!("Player attacked for {} damage!", rng.u32(10..=20));
}

fn main() {
    App::new()
        .add_plugins(RngPlugin::default())
        .add_systems(Startup, setup_player)
        .add_systems(Update, do_damage)
        .run();
}

Deterministic RNG

In order to obtain determinism for your game/app, the Rng's must be seeded. GlobalRng and RngPlugin can be given a seed which then sets the internal PRNG to behave deterministically. Instead of having to seed every RngComponent manually, as long as the GlobalRng is seeded, then RngComponent can be created directly from the global instance, cloning the internal Rng to itself, which gives it a random but deterministic seed. This allows for better randomised states among RngComponents while still having a deterministic app.

Systems also must be ordered correctly for determinism to occur. Systems however do not need to be strictly ordered against every one as if some linear path. Only related systems that access a given set of RngComponents need to be ordered. Ones that are unrelated can run in parallel and still yield a deterministic result. So systems selecting a Player entity with a RngComponent should all be ordered against each other, but systems selecting an Item entity with an RngComponent that never interacts with Player don't need to be ordered with Player systems, only between themselves.

To see an example of this, view the project's tests to see how to make use of determinism for testing random systems.

Supported Versions

bevy_turborand bevy
v0.10 v0.15
v0.9 v0.14
v0.8 v0.13
v0.7 v0.12
v0.6 v0.11
v0.5 v0.10
v0.4 v0.9
v0.2, v0.3 v0.8
v0.1 v0.7

MSRV for bevy_turborand is the same as in bevy, so always the latest Rust compiler version.

Migration Guide from 0.2 to 0.3

With turborand 0.6, there are a lot of breaking changes due to a rework of the API. For the most part, this is mostly internal to turborand and bevy_turborand exposes the new traits by default, so any existing code should more or less work fine, except for the following:

  • from_global on RngComponent no longer exists. Instead, there are From implementations on RngComponent and ChaChaRngComponent that cover more use-cases where a reference or resource or even another component could be used for initialising a new RngComponent and so forth. This makes it more flexible with regards to what the source is. Just go to town with RngComponent::from.
  • new functions no longer accept an Option parameter for the seed. The methods are split between new for initialising without a seed (so obtaining a random seed), and with_seed for initialising with a seed value (which applies to components and resources).
  • RngPlugin now uses a builder pattern to initialise. new creates a default state with no seeds applied, and then with_rng_seed and with_chacha_seed applies seed values to the plugin to then initialise the global RNG resources with. See the docs for an example of how that might look now.
  • bevy_turborand now has a number of feature flags, and apart from the new traits (which are always provided when no flags are enabled), everything else is behind a feature flag. For example, wyrand based structs (RngComponent et al) are behind the wyrand flag, which is enabled by default. For a higher quality entropy source (though it will be slower), chacha flag provides RNG provided by the ChaCha8 algorithm, such as ChaChaRngComponent. RngPlugin is available when either wyrand or chacha is enabled. Otherwise, existing flags like rand enable the rand crate compatibility layer, and serialize for serde derives.

Migration Guide from 0.3 to 0.4

bevy_turborand moves to turborand 0.8, which rolls with a couple of major API breaking changes. Certain traits are no longer exposed as they are internal implementation details. The main changes are that ChaChaRng is no longer backed by a Vec for buffering entropy, switching to an aligned array for improving generation throughput at the slight cost of initialisation performance and struct size. It does mean no need for the single heap allocation however when the RNG generates a number for the first time. This refactor also changes how ChaChaRng is serialised, so bevy_turborand 0.4 is not compatible with previously serialised data.

Also, the old Clone behaviour for TurboCore RNGs has been changed, so .clone() now maintains the state between original and cloned instances. The old behaviour now exists as the ForkableCore trait with the .fork() method, which has the original instance's state be mutated in order to derive a new random state for the forked instance. As such, RngComponent and ChaChaRngComponent can now implement Clone.

Migration Guide from 0.4 to 0.5

bevy_turborand moves to turborand 0.10, which introduces a few more sample/sample_multiple methods for supporting iterators, partial_shuffle and the major change regarding stable indexing, with sample/shuffle methods using the new index method. index allows for sampling to be stable across platforms, though it is currently optimised for 64-bit systems. All sampling/shuffling methods now make use of index, so this means bevy_turborand will be deterministic across different platforms, such as having the same output for wasm32 and x86-64. The only method that doesn't benefit from this is usize.

ChaChaRngComponent also now has a different output due to a change of the internal ChaChaRng source, which also changes how it gets serialised.

License

Licensed under either of

at your option.

Dependencies

~21–32MB
~521K SLoC