Only the best random numbers by @little cloak
https://docs.rs/bevy_rand/latest/bevy_rand/
https://docs.rs/bevy_turborand/latest/bevy_turborand/
Bevy Rand
A plugin to enable ECS optimised random number generation for the Bevy game engine.
154 messages · Page 1 of 1 (latest)
Only the best random numbers by @little cloak
https://docs.rs/bevy_rand/latest/bevy_rand/
https://docs.rs/bevy_turborand/latest/bevy_turborand/
Bevy Rand
A plugin to enable ECS optimised random number generation for the Bevy game engine.
Hello! I'm trying to work with bevy_rand in an async compute task - I fork the rng and then move it into the async task. The task owns the forked Rng, and passes out a &mut Rng to its helper functions.
I want to make these helper functions into one shot systems (for testing purposes - in game they will only be called within the async compute task). Since these are methods on something that holds a &mut Rng (or even if the object didn't hold the &mut Rng - we would pass it in as a param) - I want to implement SystemParam on &'static mut Rng/EntropyComponent.
I don't have a lot of experience implementing SystemParam so not sure if I'm doing something super cursed or if implementing SystemParam on references is okay? Then again I'm not sure if the lifetimes line up
Would appreciate your thoughts on the SystemParam part @edgy copper 🙏
One way I guess this might be done is by passing the Component' s component id (assuming it's unique per entity)🤔
@random glen Just as a thought here but, do these helper methods need to be specific about what type of Rng source they take and it being a reference? Perhaps the source/Rng can be defined with a generic or an impl so like:
fn test_rng(mut source: impl Rng) {
println!("{}", source.gen::<u64>())
}
and then using it becomes just:
test_rng(rng.fork_rng());
Keep in mind that RngCore already implements an auto trait implementation for references of an RngCore, which Rng is then implemented automatically to anything that implements RngCore. So in effect, this also works with the above idea:
let mut r = rng.fork_rng();
test_rng(&mut r);
This then at least makes your helper methods less coupled to bevy internals, lessening the need to complicate how you test them.
Just thinking out loud here
I'm procedurally generating the world in my game (long-running task that isn't required for the next frame - so it must be Async Compute).
Each task is composed of several "recipes" applied to a graph, each of which I want to be able to run and test dynamically in my test ui tool.
In the test app I can create an entity, and run one shot systems on it (press a button and run a recipe on it). The graph can be a component on the entity.
For the game however - I can't bring anything in from the world into the Async Compute task. (So the graph is created and recipes are run on it inside the task - no recipes or anything).
Maybe the solution is in running the games procedural generation using one shot systems? I just don't want the systems to be blocking the frame rate in case I want to do the generation in the background🤔
After writing this all out my issue is maybe only tangentially related to bevy_rand - maybe I should post in the ECS channel🤔
Yeah, sounds like it. The bevy_rand part seems easy to work around here, but the rest of it seems more involved
I don't think the lifetimes will work
Hello, I would like to randomly choose an item in a vector with a bias or weight with a function like rand's choose_weighted(). But I also would like to use Bevy's bevy_rand crate's facilities for using PRNGs with the ECS system, and it does not seem to include a choose_weighted()-like function. Am I mistaken? Is there a recommended Bevy way to do that?
@thick mirage You just pull in SliceRandom trait from rand crate and get in there with .choose_weighted(). bevy_rand is just meant to make it easier to have PRNGs integrated into the ECS via Resource/Components, and then you can just use all the goodies from the rand ecosystem without all that needing to be implemented/exported.
Thank you... I am new to Rust and Bevy. With Bevy I can easily get a ResMut<GlobalEntropy<WyRand>> ressource, that forks into a RngCore which is the type of rand_core's generators. Given that, how do I "pull the SliceRandom trait" from rand to be able to use choose_weighted()?
You'll need to add the rand crate, and then use rand::prelude::SliceRandom. bevy_rand depends on rand_core only, not rand in order to minimise the dep size
Then you can use the resource directly as that implements RngCore and can be provided to the choose_weighted method without needing to fork
Great! Thank you very much. I wish you a nice day Sachymetsu.
I was having similar ideas, I will look at that in more detail tomorrow
For any folks paying attention, I've got a bevy_rand PR to basically rework my global resources to being just... entities and Single queries https://github.com/Bluefinger/bevy_rand/pull/35
The rationale is to basically unify the behaviour/APIs around seeding/reseeding to make use of component hooks for RngSeed/Entropy sources. This means it is easier to support and reason about the way global and entity local sources interact. Plus, with some type aliases as helpers, accessing GlobalEntropy sources is now less verbose, without needing to use ResMut.
For those that wish to help test out these potential changes and help polish the code/docs, feel free to look at the PR and pull the changes (if you are living dangerously on the bevy main branch).
@little cloak I really like the Global marker decision, were you biased in any way towards it or is it something you came up with naturally?
@untold solar Honestly, after I messed with the observer feature code, and saw how nice the whole RngSeed+Entropy components combo worked, the resource version of GlobalEntropy needing to have both Seed AND the Rng instance contained within the same resource kinda irked me
Not having similar APIs and behaviours between the two just stuck out as a sore thumb
Mhm
I'll need to look in depth how you expose initialization for the global resource
And with Single queries being a thing, it felt less necessary to have a dedicated Resource
Also, having "required resources" seems very nice
That said, we need to ensure there is only one Global marked entity for that to work correctly
And that will be ported to bevy core eventually
Single helps mitigate that by warning/panicking if that criteria isn't met
yeah, but no need to give users more branches to trip on
If I had resources as entities, then I could move back to having Resources for global sources, because then it'll all share the same hooks/requirements/behaviours
It's easier to manage footguns of just one kind of API instead of two different APIs
The plan is to replace Res<T> with Single<T, With<Global>> internally
When default query filters are added, we'll do Without<Global> as default, so people can opt-in to processing resource-entities
Yes, pretty much. Also, having RngSeed be a separate component and an immutable one also makes handling the hooks/behaviour of reseeding pretty solid
Though, Entropy is kinda a weird component where you can't really modify its state directly, but all its methods are &mut, and it''ll be really weird needing to track change detection on it given all PRNG internal states are black boxes
I'd love to opt out of change detection by default for Entropy
though that's microoptimisation territory
In any case, I don't plan to merge that PR until after Xmas, maybe after the New Year, so to give anyone a chance to test out the changes and report back
Then I'll cut a new release early jan
could use a cell to hide state
still require &mut to ensure singular borrows
My experience with bevy_turborand with that was rather painful, it's not worth going the interior mutability route given you need &mut to ensure singular borrows
And another one if folks are interested: no_std bevy_rand, since all the work with bushrat shouldn't go unnoticed
In prep for a new release, I'm going ahead and merging the anti-resource PR, since I've gotten no further feedback or issues, and it'll be going into a new major version with a migration path.
bevy_rand is now no_std compatible on the main branch. Feel free to test it out if living on the bleeding edge
I see the crate seems focused on low level random stuff like entropy, seeding etc..
Is there existing support or planned support for things like helper functions for vectors, noise generation (perlin, simplex, etc..) ?
Would be nice to have a one stop shop for all of our random needs.
@fallen sparrow For helper functions, you can just hook into the rand ecosystem, like rand_distr and so forth. Anything that builds on top of RngCore will work with bevy_rand.
Stuff like perlin/simplex noise is not a PRNG, and has different purpose and output to something like PRNG algorithms like WyRand, Xoshiro, and so forth.
bevy_math should have extension methods that accept RngCore trait objects/impls, so it already integrates fine there
Been hitting the getrandom RUSTFLAG thing a lot while working on no_std for bevy_asset. Is there any movement to try and "fix" this using optional dependencies for wasm_js, custom, and rdrand/rndr backends? They could literally be empty crates which just enable the required RUSTFLAGS line.
# bevy_utils Cargo.toml
[features]
getrandom-opt-in-backends = [
"dep:bevy_getrandom_backend_web",
"dep:bevy_getrandom_backend_asm",
]
getrandom-custom = [
"dep:bevy_getrandom_backend_custom",
]
[target.'cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))'.dependencies]
bevy_getrandom_backend_web = { version = ..., optional = true }
# ...
Asking here because while I think this should be fixed within Bevy the technique might make more sense to try out in bevy_rand first. Also, @little cloak is obviouslySME-Rand lol
@tacit plover empty crates technique for getting round getrandom backend nonsense? Lmao, the rand folks are going to love this. Probably can give this a try with bevy_rand first for sure.
It's kinda like you've made a crate which provides a getrandom backend...it just provides one of the opt-in ones haha
If this works out, I am inclined to then create an issue on getrandom to highlight this as a problem with their approach, which was to prevent backends from being activated via feature flags
Because if it can be done anyway, then why have this ergonomic hit in the first place?
Exactly. I'm not even sure there is a way to prevent this
I think there needs to be an enforceable application only flags and library flags, but that's a rust/cargo problem, not a crate problem
Yeah definitely, it'd be great to have bin-only flags, and also test-only, example-only, etc.
That would be lovely, for sure.
If I understand it, the empty crate has a build.rs file to enable the flags? Or something I am not understanding here?
Nah not even, just needs to have the .cargo/config.toml file with the required flags IIUC
I believe library crates can define those, but I may be mistaken
In which case, a build.rs would work too
I will try both, but yeah. Rn, just waking up, so brain not yet fully functioning lmao. Gotta feed the damn cats
Right, I am trying this out with my bevy_rand workspace, but I don't think the empty crate approach is quite working
I still get compilation failures, with a build script or otherwise
I wonder if the build script is "scoped" to just the crate itself and not the overall build environment
I might be doing something wrong in any case tho
Yeah I was just testing it myself, it appears to not work as I hoped and it does scope the variables to avoid cross contamination (which I guess this is)
I really thought this would work, I'm surprised a crate can't define some flags that need to be used for one of its dependencies
The rand folks did their homework it seems
Annoyingly so!
Still, I'm inclined to raise an issue anyway given the ergonomic hit this has on bigger, more complex projects
Though if I do, it would be nice if I'm not the only one adding data to such an issue
Yeah it's frustrating. critical-section has this same problem (needing to link against an extern item) but doesn't have the same ergonomics pain
Yeah, I think we would have to give a counter example of a crate facing the same issue but resolving it differently
critical-section seems like a good one to provide
I think the change getrandom should do is move all the opt-in backends into a new dependency, getrandom-experimental-backends or whatever name, and then have custom be the default backend if none is selected using a feature on getrandom itself. This would then allow external crates to provide the custom implementation
Probably the issue should have a name like "improving the backend ergonomics story", something less negative to hopefully make it more amenable for discussion. I know that this was a contentious change on getrandom's part so I'm quite certain they are wary of further drama on this topic
Yeah I'm checking https://github.com/rust-random/getrandom/issues/658. The team agrees it's not ideal, but they really want to make sure a dependency can't silently change the implementation, which makes sense from a security perspective
So yeah, any change we propose kinda has to provide that assurance
Or at least be a visible opt-in that can be easy to audit (so if something does break, it can be triaged quickly)
The backend crate idea would be visible on the cargo tree output, for example
Might even make sense to split getrandom into getrandom-core and mutually exclusive getrandom-x crates. Nth mentioned that if you declare a package.links value in Cargo.toml, Rust will refuse to compile your crate in cases where multiple dependencies have the same package.links value
So getrandom-core is the basic API, getrandom-x is some official backend x, and getrandom is the collection of getrandom-x crates as features
I think embassy makes use of that as well, because they have feature flags that definitely are mutually exclusive for configuring their executor
I think, I could be wrong
There's definitely options here
But yeah, it just could be better, because it's not great having to spam env flags just to get WASM test builds to run
checking critical-section/embassy-executor, they just opt for checking all exclusive feature flag permutations
embassy-executor has a macro for this
Is there a crate making use of the package.links feature?
Not that I'm aware of, but I don't actually think it's required. I've forked getrandom and I'm experimenting with the idea I had. Since the backends need to define a no_mangle symbol, if you ever had two implementations active, it'd fail to link anyway
If your idea pans out, then it will be a breaking change for getrandom to accept. Maybe we will wait a year or two before that hits a release 😅
Yeah I doubt they'd change quickly. But, if it's as much of an ergonomic improvement as I think it'll be (for developing backends and consumers), maybe it'll make it haha
Yeah, I'd hope they'd course-correct sooner rather than later
Also, if you have a branch up, I can do a test run with a bevy_rand branch to see if that works out or not
Ok it's not done yet, but the rough shape works here
A small cross-platform library for retrieving random data from (operating) system source - GitHub - bushrat011899/getrandom at wip_split_getrandom
The main getrandom package isn't changed yet, I've just carved out getrandom-core and migrated the Windows 10 backend to the new structure
Mostly just feeling out if this is an ergonomic path forward
So far, it feels much nicer
All a custom backend has to do is implement getrandom_core::Backend, and call set_backend!(MyCustomBackend) to setup the appropriate linking
Zero invocations of that macro is a linking error for missing symbols (exactly like not backend selected/available in getrandom 0.3). Two or more is also a linking error for duplicate symbols, preventing the kind of supply chain attack/implicit change the maintainers are worried about.
Looking at the windows backend, it looks pretty clean and straightforward
Is there a way to ensure each forked entropy gets the same seed as the original?
I want to be able to feed the global one with seed and have the same seed distibuted between all entropies.
I initialize it with: app.add_plugins(EntropyPlugin::<ChaCha8Rng>::with_seed(seed));
But then when I use fork_rng() or fork_seed() it creates an entropy with a different seed (checked with get_seed())
@main hedge normally you don't want seeds to all be the same across different RNGs, and forking is meant for this use case. If for whatever reason you do want seeds to be the same, just .clone(). Simples.
Thanks!
I'm in the process of making randomly generated dungeons, so I want to be able to regenerate faulty ones, so I'm logging faulty seeds and then initializing the entropies with them
I see I see. Let me know how that goes and if you encounter any problems
I will
Thanks!
Could it be that ::from_seed() also changes the seed?
and with_seed()
I'm trying to figure out a way to provide the entropy with that exact external seed
Well
It seems that's not up to you
If you did the plugin initialisation with with_seed, then the GlobalEntropy will have the set seed
Like, it seems that the structs the crate wraps already generate something different than the provided seed
I'll give it another try and put the logs back in?
Depending on the PRNG, the internal state of the PRNG will not equal exactly to the given seed. The seed simply provides entropy/randomness for the internal state to be configured to
So, as long as you give it the same seed, it'll result in the same internal state
Oh right
I could just not use ChaCha8Rng
I'll look for one that takes the exact seed and provided and also exposes one
Like
It's not your wrappers' fault
Thanks for making the crate btw
I'll be back with more information in the future, which will hopefully be something like "yay, solved"
More complex PRNGs like ChaCha have more complex internal states. I don't think you should worry/care about the internal state of a PRNG, as long as you know its initial state (the seed), as all supported algos for bevy_rand are deterministic
That's why I split the Seed/Entropy Components, makes it easier to track that initial state
Is there a way to get the entropy's seed directly?
I just realized that the entropy references from which I extract the seed have to be mutable so I wonder if they, too, make a new seed when I'm trying to fetch it
pub fn get_seed(&mut self) -> [u8; 32] {
self.0.fork_seed().clone_seed()
}```
Does this code make new seeds?
There's the RngSeed component, and you shouldn't need &mut to just clone seeds.
(alright, checking)
RngSeed is immutable, because it represents the initial seed for an Entropy component, so it will only allow & and not &mut
For the global entropy, there's the system param: GlobalRngEntity<Rng>
Inserting RngSeed on an entity will automatically initialise Entropy component on that same entity
That did it!
Thanks a lot!
🎉
No worries, glad to be of help!
Is there anything relationship-related with the entropies?
When despawning my entropies I get:
```2025-06-27T20:08:53.952981Z WARN bevy_ecs::error::handler: Encountered an error in command <bevy_ecs::system::commands::entity_command::remove<bevy_rand::component::E ntropy<bevy_prng::chacha::ChaCha8Rng>>::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}: The entity with ID 8v1 does not exist (enable track_location feature for more details)
Looks like it's trying to despawn the entropy after it's already been despawned or something?
Removing a RngSeed component from an entity will try to remove the Entropy component, so when despawning a whole entity at once, it might clean things up before RngSeed's hooks can run. Since it is just a warning, you can probably ignore that
It's a giant warning though (I didn't paste most of it)
I guess the best solution would be to remove the RngSeed first and only then despawn the entity?
I think that's something I can fix anyway, since the warning in this case is not even useful anyway.
I've published a patch release, v0.11.1 for bevy_rand which should silence that warning when despawning entities with RngSeed on them
Will give it a try soon, thanks 🙏
It no longer throws that giant warning
Thanks!
Is there any reason (it seems that) I’m forced to rely on a component hook to have my source’s Entropy component added? from_seed is not public so I can’t seem to construct my own Entropy component from my seed.
Use case is that I have an rng seed u32 which I have saved in my save game. I want to add a source to my
Army entity but also have immediate access to the Entropy variable so I can fork it for each Regiment in the Army. The army and regiment creation happen in the same function so I’m wanting to add all these immediately rather than have it deferred.
Maybe I’m missing something. I’m still on bevy_rand 0.9 but I noticed the code here seems to be the same on the latest version too.
Edit: Looks like this works.
let seed: u64 = 234; // TODO: Get from save game.
let army_rng_seed = RngSeed::<WyRand>::from_seed(seed.to_ne_bytes());
let mut army_rng = Entropy::new(WyRand::from_seed(army_rng_seed.clone_seed()));
commands.spawn((
Name::new("Army"),
army_rng_seed,
army_rng.clone(),
));
for r in regiments.iter() {
commands.spawn((
Name::new("Regiment"),
army_rng.fork_rng(),
));
}
Because in the absence of Required Components + Construct, I have to use hooks to create the link between initial seed (immutable) and the rng component. And instead of forking the rng, you should be able to fork the seed component instead.
In any case, newer versions of bevy_rand solve the manual linking of child Rng sources by using relations, so you can just "link" an entity to get a forked seed/rng from a parent source
for v0.9, there is an experimental feature to enable this, but the API for it is a bit verbose/not great.
The benefit of the relations approach is that you just update the parent entity with a new seed component, and everything downstream gets updated automatically
Right. I should revisit this as relationships once I can upgrade to the 0.16+ version.
FWIW I’m currently not using relations (or hierarchy) for this Army <—> Regiment relationship.
I’ll see if I can fork the seed. Thanks for the tip!
Looks like removing the "-dev" from everything and bevy_rand passes all tests for 0.18
Yeah, https://github.com/Bluefinger/bevy_rand/pull/63 just running the CI on this
Until rand updates, I think bevy_rand as is can be considered stable-ish. I don't think I'll be changing the APIs much