Skip to content

Commit

Permalink
refactor: Tidy up observer code with single/populated system params (#32
Browse files Browse the repository at this point in the history
)
  • Loading branch information
Bluefinger authored Dec 3, 2024
1 parent 8e99f7e commit a66ff27
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 78 deletions.
130 changes: 54 additions & 76 deletions src/observers.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use std::marker::PhantomData;

use bevy_ecs::{
prelude::{
Commands, Component, Entity, EntityWorldMut, Event, OnInsert, Query, ResMut, Trigger, With,
},
query::QuerySingleError,
prelude::{Commands, Component, Entity, Event, OnInsert, ResMut, Trigger, With},
query::Without,
system::{Populated, Single},
};

use bevy_prng::SeedableEntropySource;
Expand Down Expand Up @@ -129,101 +128,80 @@ pub fn seed_from_global<Rng: SeedableEntropySource>(
}
}

/// Observer System for pulling in a new seed for the current entity from its parent Rng source.
/// Observer System for pulling in a new seed for the current entity from its parent Rng source. This
/// observer system will only run if there are parent entities to have seeds pulled from.
pub fn seed_from_parent<Rng: SeedableEntropySource>(
trigger: Trigger<SeedFromParent<Rng>>,
q_linked: Populated<&RngParent<Rng>>,
mut q_parents: Populated<&mut EntropyComponent<Rng>, With<RngChildren<Rng>>>,
mut commands: Commands,
) where
Rng::Seed: Send + Sync + Clone,
{
let target = trigger.entity();

if target != Entity::PLACEHOLDER {
commands.entity(target).queue(|mut entity: EntityWorldMut| {
let Some(parent) = entity.get::<RngParent<Rng>>().map(|parent| parent.entity()) else {
return;
};
entity
.world_scope(|world| {
world.get_entity_mut(parent).ok().and_then(|mut parent| {
parent
.get_mut::<EntropyComponent<Rng>>()
.map(|mut rng| rng.fork_seed())
})
})
.map(|seed| entity.insert(seed));
});
if let Ok(mut rng) = q_linked
.get(target)
.and_then(|parent| q_parents.get_mut(parent.entity()))
{
commands.entity(target).insert(rng.fork_seed());
}
}

/// Observer System for handling seed propagation from source Rng to all child entities.
/// Observer System for handling seed propagation from source Rng to all child entities. This observer
/// will only run if there is a single source entity and also if there are target entities to seed.
pub fn seed_children<Source: Component, Target: Component, Rng: SeedableEntropySource>(
trigger: Trigger<OnInsert, EntropyComponent<Rng>>,
mut q_source: Query<&mut EntropyComponent<Rng>, (With<Source>, With<RngChildren<Rng>>)>,
q_target: Query<Entity, With<Target>>,
q_source: Single<
(Entity, &mut EntropyComponent<Rng>),
(With<Source>, With<RngChildren<Rng>>, Without<Target>),
>,
q_target: Populated<Entity, (With<Target>, With<RngParent<Rng>>, Without<Source>)>,
mut commands: Commands,
) where
Rng::Seed: Send + Sync + Clone,
{
let source = trigger.entity();

if source != Entity::PLACEHOLDER {
if let Ok(mut rng) = q_source.get_mut(source) {
let batch: Vec<(Entity, RngSeed<Rng>)> = q_target
.iter()
.map(|target| (target, rng.fork_seed()))
.collect();

commands.insert_batch(batch);
}
let (source, mut rng) = q_source.into_inner();
// Check whether the triggered entity is a source entity. If not, do nothing otherwise we
// will keep triggering and cause a stack overflow.
if source == trigger.entity() {
let batch: Vec<(Entity, RngSeed<Rng>)> = q_target
.iter()
.map(|target| (target, rng.fork_seed()))
.collect();

commands.insert_batch(batch);
}
}

/// Observer System for handling linking a source Rng with all target entities. Highly recommended
/// that the Source Rng is unique, or has a marker component that designates it as unique, otherwise
/// this observer will pick whichever get queried first during linking.
/// Observer System for handling linking a source Rng with all target entities. This observer will only
/// run if there is a single source entity and if there are target entities to link with. If these assumptions
/// are not met, the observer system will not run.
pub fn link_targets<Source: Component, Target: Component, Rng: SeedableEntropySource>(
_trigger: Trigger<LinkRngSourceToTarget<Source, Target, Rng>>,
q_source: Query<Entity, With<Source>>,
q_target: Query<Entity, With<Target>>,
q_source: Single<Entity, (With<Source>, Without<Target>)>,
q_target: Populated<Entity, (With<Target>, Without<Source>)>,
mut commands: Commands,
) {
let source = match q_source.get_single() {
Ok(parent) => Some(parent),
// If we somehow have more than one source, just use the first one and stick with that.
Err(QuerySingleError::MultipleEntities(_)) => q_source.iter().next(),
Err(QuerySingleError::NoEntities(_)) => None,
};

if let Some(parent) = source {
let mut targets = q_target.iter();

let assigned = match targets.size_hint().0 {
0 => false,
1 => {
let target = targets.next().unwrap();

commands
.entity(target)
.insert(RngParent::<Rng>::new(parent));

true
}
_ => {
let targets: Vec<_> = targets
.map(|target| (target, RngParent::<Rng>::new(parent)))
.collect();

commands.insert_batch(targets);

true
}
};

if assigned {
commands
.entity(parent)
.insert(RngChildren::<Rng>::default());
}
let parent = q_source.into_inner();

let mut targets = q_target.iter();

if targets.size_hint().0 == 1 {
let target = targets.next().unwrap();

commands
.entity(target)
.insert(RngParent::<Rng>::new(parent));
} else {
let targets: Vec<_> = targets
.map(|target| (target, RngParent::<Rng>::new(parent)))
.collect();

commands.insert_batch(targets);
}

commands
.entity(parent)
.insert(RngChildren::<Rng>::default());
}
5 changes: 4 additions & 1 deletion src/seed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ where
fn register_component_hooks(hooks: &mut bevy_ecs::component::ComponentHooks) {
hooks
.on_insert(|mut world, entity, _| {
let seed = world.get::<RngSeed<R>>(entity).unwrap().seed.clone();
let seed = world
.get::<RngSeed<R>>(entity)
.map(|seed| seed.clone_seed())
.unwrap();
world
.commands()
.entity(entity)
Expand Down
6 changes: 5 additions & 1 deletion tests/integration/reseeding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,11 @@ fn component_fork_as_seed() {
fn observer_global_reseeding() {
use bevy_app::prelude::{PostUpdate, PreUpdate, Startup};
use bevy_ecs::prelude::{Entity, With};
use bevy_rand::{observers::ReseedRng, seed::RngSeed, traits::{ForkableInnerSeed, SeedSource}};
use bevy_rand::{
observers::ReseedRng,
seed::RngSeed,
traits::{ForkableInnerSeed, SeedSource},
};

let seed = [2; 8];

Expand Down

0 comments on commit a66ff27

Please sign in to comment.