Bluefinger / bevy_turborand

A plugin to enable random number generation for the Bevy game engine.
Apache License 2.0
37 stars 6 forks source link

bevy_turborand

CI License Cargo Documentation

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.9.0-rc.1 v0.14.0-rc.4
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:

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.