Skip to content

Commit

Permalink
Merge pull request #23 from Bluefinger/seed-sources
Browse files Browse the repository at this point in the history
feat: RngSeed & ForkableSeed traits
  • Loading branch information
Bluefinger authored Jul 23, 2024
2 parents e132b79 + 6836efe commit abfd069
Show file tree
Hide file tree
Showing 14 changed files with 477 additions and 155 deletions.
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = ["Gonçalo Rica Pais da Silva <[email protected]>"]
edition = "2021"
repository = "https://github.com/Bluefinger/bevy_rand"
license = "MIT OR Apache-2.0"
version = "0.7.1"
version = "0.8.0"
rust-version = "1.76.0"

[workspace.dependencies]
Expand Down Expand Up @@ -45,7 +45,7 @@ wyrand = ["bevy_prng/wyrand"]
[dependencies]
# bevy
bevy.workspace = true
bevy_prng = { path = "bevy_prng", version = "0.7" }
bevy_prng = { path = "bevy_prng", version = "0.8" }

# others
getrandom = "0.2"
Expand All @@ -59,10 +59,10 @@ serde_derive = { workspace = true, optional = true }
# cannot be out of step with bevy_rand due to dependencies on traits
# and implementations between the two crates.
[target.'cfg(any())'.dependencies]
bevy_prng = { path = "bevy_prng", version = "=0.7" }
bevy_prng = { path = "bevy_prng", version = "=0.8" }

[dev-dependencies]
bevy_prng = { path = "bevy_prng", version = "0.7", features = ["rand_chacha", "wyrand"] }
bevy_prng = { path = "bevy_prng", version = "0.8", features = ["rand_chacha", "wyrand"] }
rand = "0.8"
ron = { version = "0.8.0", features = ["integer128"] }

Expand Down
4 changes: 4 additions & 0 deletions MIGRATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ This **will** change the type path and the serialization format for the PRNGs, b
## Migrating from v0.5 to v0.6

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.

## Migrating from v0.7 to v0.8

`GlobalRngSeed` has been changed to make use of `SeedSource` trait, for things like instantiation: `new` is now `from_seed`. `get_seed` is now `clone_seed`. Most of these changes can be done easily by importing the `SeedSource` trait.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ All supported PRNGs and compatible structs are provided by the `bevy_prng` crate
#### `bevy_rand` feature activation
```toml
rand_core = "0.6"
bevy_rand = { version = "0.7", features = ["rand_chacha", "wyrand"] }
bevy_rand = { version = "0.8", features = ["rand_chacha", "wyrand"] }
```

#### `bevy_prng` feature activation
```toml
rand_core = "0.6"
bevy_rand = "0.7"
bevy_prng = { version = "0.7", features = ["rand_chacha", "wyrand"] }
bevy_rand = "0.8"
bevy_prng = { version = "0.8", features = ["rand_chacha", "wyrand"] }
```

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 (for security, etc), then use `rand_chacha`. For more information, [go here](https://docs.rs/bevy_rand/latest/bevy_rand/tutorial/ch01_choosing_prng/index.html).
Expand Down Expand Up @@ -128,7 +128,7 @@ fn setup_npc_from_source(

| `bevy` | `bevy_rand` |
| ------ | ------------ |
| v0.14 | v0.7 |
| v0.14 | v0.7 - v0.8 |
| v0.13 | v0.5 - v0.6 |
| v0.12 | v0.4 |
| v0.11 | v0.2 - v0.3 |
Expand All @@ -138,7 +138,7 @@ The versions of `rand_core`/`rand` that `bevy_rand` is compatible with is as fol

| `bevy_rand` | `rand_core` | `rand` |
| ------------ | ----------- | ------ |
| v0.1 -> v0.7 | v0.6 | v0.8 |
| v0.1 -> v0.8 | v0.6 | v0.8 |

## Migrations

Expand Down
38 changes: 28 additions & 10 deletions src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ use std::fmt::Debug;

use crate::{
resource::GlobalEntropy,
traits::{EcsEntropySource, ForkableAsRng, ForkableInnerRng, ForkableRng},
seed::RngSeed,
traits::{
EcsEntropySource, ForkableAsRng, ForkableAsSeed, ForkableInnerRng, ForkableRng,
ForkableSeed,
},
};
use bevy::prelude::{Component, Mut, Reflect, ReflectComponent, ReflectFromReflect, ResMut};
use bevy_prng::SeedableEntropySource;
Expand Down Expand Up @@ -160,6 +164,11 @@ impl<R: SeedableEntropySource + 'static> SeedableRng for EntropyComponent<R> {
Self::new(R::from_seed(seed))
}

#[inline]
fn from_rng<S: RngCore>(rng: S) -> Result<Self, rand_core::Error> {
R::from_rng(rng).map(Self::new)
}

/// Creates a new instance of the RNG seeded via [`ThreadLocalEntropy`]. This method is the recommended way
/// to construct non-deterministic PRNGs since it is convenient and secure. It overrides the standard
/// [`SeedableRng::from_entropy`] method while the `thread_local_entropy` feature is enabled.
Expand All @@ -171,14 +180,8 @@ impl<R: SeedableEntropySource + 'static> SeedableRng for EntropyComponent<R> {
#[cfg(feature = "thread_local_entropy")]
#[cfg_attr(docsrs, doc(cfg(feature = "thread_local_entropy")))]
fn from_entropy() -> Self {
let mut seed = Self::Seed::default();

// Source entropy from thread local user-space RNG instead of
// system entropy source to reduce overhead when creating many
// rng instances for many entities at once.
ThreadLocalEntropy::new().fill_bytes(seed.as_mut());

Self::from_seed(seed)
// This operation should never yield Err on any supported PRNGs
Self::from_rng(ThreadLocalEntropy::new()).unwrap()
}
}

Expand Down Expand Up @@ -233,6 +236,21 @@ where
type Output = R;
}

impl<R> ForkableSeed<R> for EntropyComponent<R>
where
R: SeedableEntropySource + 'static,
R::Seed: Send + Sync + Clone,
{
type Output = RngSeed<R>;
}

impl<R> ForkableAsSeed<R> for EntropyComponent<R>
where
R: SeedableEntropySource + 'static,
{
type Output<T> = RngSeed<T> where T: SeedableEntropySource, T::Seed: Send + Sync + Clone;
}

#[cfg(test)]
mod tests {
use bevy::reflect::TypePath;
Expand Down Expand Up @@ -296,7 +314,7 @@ mod tests {
#[test]
fn rng_untyped_serialization() {
use bevy::reflect::{
serde::{ReflectSerializer, ReflectDeserializer},
serde::{ReflectDeserializer, ReflectSerializer},
TypeRegistry,
};
use ron::to_string;
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![warn(clippy::undocumented_unsafe_blocks)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, allow(unused_attributes))]
#![deny(missing_docs)]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]

/// Components for integrating [`RngCore`] PRNGs into bevy. Must be newtyped to support [`Reflect`].
Expand Down
19 changes: 13 additions & 6 deletions src/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use crate::{component::EntropyComponent, resource::GlobalEntropy, seed::GlobalRngSeed};
use crate::{
component::EntropyComponent,
resource::GlobalEntropy,
seed::{GlobalRngSeed, RngSeed},
traits::SeedSource,
};
use bevy::{
prelude::{App, Plugin},
reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath},
Expand Down Expand Up @@ -68,16 +73,18 @@ where
{
fn build(&self, app: &mut App) {
app.register_type::<GlobalEntropy<R>>()
.register_type::<EntropyComponent<R>>();

GlobalRngSeed::<R>::register_type(app);
.register_type::<EntropyComponent<R>>()
.register_type::<GlobalRngSeed<R>>()
.register_type::<R::Seed>();

if let Some(seed) = self.seed.as_ref() {
app.insert_resource(GlobalRngSeed::<R>::new(seed.clone()));
app.insert_resource(GlobalRngSeed::<R>::from_seed(seed.clone()));
} else {
app.init_resource::<GlobalRngSeed<R>>();
}

app.init_resource::<GlobalEntropy<R>>();
app.init_resource::<GlobalEntropy<R>>()
.world_mut()
.register_component_hooks::<RngSeed<R>>();
}
}
6 changes: 4 additions & 2 deletions src/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
pub use crate::component::EntropyComponent;
pub use crate::plugin::EntropyPlugin;
pub use crate::resource::GlobalEntropy;
pub use crate::seed::GlobalRngSeed;
pub use crate::traits::{ForkableAsRng, ForkableInnerRng, ForkableRng};
pub use crate::seed::{GlobalRngSeed, RngSeed};
pub use crate::traits::{
ForkableAsRng, ForkableAsSeed, ForkableInnerRng, ForkableRng, ForkableSeed, SeedSource
};
#[cfg(feature = "wyrand")]
#[cfg_attr(docsrs, doc(cfg(feature = "wyrand")))]
pub use bevy_prng::WyRand;
Expand Down
41 changes: 29 additions & 12 deletions src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ use std::fmt::Debug;

use crate::{
component::EntropyComponent,
seed::GlobalRngSeed,
traits::{EcsEntropySource, ForkableAsRng, ForkableInnerRng, ForkableRng},
seed::{GlobalRngSeed, RngSeed},
traits::{
EcsEntropySource, ForkableAsRng, ForkableAsSeed, ForkableInnerRng, ForkableRng,
ForkableSeed, SeedSource,
},
};
use bevy::{
ecs::world::{FromWorld, World},
Expand Down Expand Up @@ -88,7 +91,7 @@ where
{
fn from_world(world: &mut World) -> Self {
if let Some(seed) = world.get_resource::<GlobalRngSeed<R>>() {
Self::new(R::from_seed(seed.get_seed()))
Self::new(R::from_seed(seed.clone_seed()))
} else {
Self::from_entropy()
}
Expand Down Expand Up @@ -125,6 +128,11 @@ impl<R: SeedableEntropySource + 'static> SeedableRng for GlobalEntropy<R> {
Self::new(R::from_seed(seed))
}

#[inline]
fn from_rng<S: RngCore>(rng: S) -> Result<Self, rand_core::Error> {
R::from_rng(rng).map(Self::new)
}

/// Creates a new instance of the RNG seeded via [`ThreadLocalEntropy`]. This method is the recommended way
/// to construct non-deterministic PRNGs since it is convenient and secure. It overrides the standard
/// [`SeedableRng::from_entropy`] method while the `thread_local_entropy` feature is enabled.
Expand All @@ -136,14 +144,8 @@ impl<R: SeedableEntropySource + 'static> SeedableRng for GlobalEntropy<R> {
#[cfg(feature = "thread_local_entropy")]
#[cfg_attr(docsrs, doc(cfg(feature = "thread_local_entropy")))]
fn from_entropy() -> Self {
let mut seed = Self::Seed::default();

// Source entropy from thread local user-space RNG instead of
// system entropy source to reduce overhead when creating many
// rng instances for many resources at once.
ThreadLocalEntropy::new().fill_bytes(seed.as_mut());

Self::from_seed(seed)
// This operation should never yield Err on any supported PRNGs
Self::from_rng(ThreadLocalEntropy::new()).unwrap()
}
}

Expand Down Expand Up @@ -182,6 +184,21 @@ where
type Output = R;
}

impl<R> ForkableSeed<R> for GlobalEntropy<R>
where
R: SeedableEntropySource + 'static,
R::Seed: Send + Sync + Clone,
{
type Output = RngSeed<R>;
}

impl<R> ForkableAsSeed<R> for GlobalEntropy<R>
where
R: SeedableEntropySource + 'static,
{
type Output<T> = RngSeed<T> where T: SeedableEntropySource, T::Seed: Send + Sync + Clone;
}

#[cfg(test)]
mod tests {
use bevy::reflect::TypePath;
Expand Down Expand Up @@ -246,7 +263,7 @@ mod tests {
#[test]
fn rng_untyped_serialization() {
use bevy::reflect::{
serde::{ReflectSerializer, ReflectDeserializer},
serde::{ReflectDeserializer, ReflectSerializer},
TypeRegistry,
};
use ron::ser::to_string;
Expand Down
Loading

0 comments on commit abfd069

Please sign in to comment.