Skip to content

[pull] main from bevyengine:main #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3344,6 +3344,17 @@ description = "Illustrates creating and updating text"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "text_background_colors"
path = "examples/ui/text_background_colors.rs"
doc-scrape-examples = true

[package.metadata.example.text_background_colors]
name = "Text Background Colors"
description = "Demonstrates text background colors"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "text_debug"
path = "examples/ui/text_debug.rs"
Expand Down
39 changes: 37 additions & 2 deletions crates/bevy_ecs/src/entity/map_entities.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
pub use bevy_ecs_macros::MapEntities;
use indexmap::IndexSet;

use crate::{
entity::{hash_map::EntityHashMap, Entity},
identifier::masks::{IdentifierMask, HIGH_MASK},
world::World,
};

use alloc::{collections::VecDeque, vec::Vec};
use alloc::{
collections::{BTreeSet, VecDeque},
vec::Vec,
};
use bevy_platform::collections::HashSet;
use core::hash::BuildHasher;
use core::{hash::BuildHasher, mem};
use smallvec::SmallVec;

use super::EntityIndexSet;

/// Operation to map all contained [`Entity`] fields in a type to new values.
///
/// As entity IDs are valid only for the [`World`] they're sourced from, using [`Entity`]
Expand Down Expand Up @@ -72,6 +78,34 @@ impl<S: BuildHasher + Default> MapEntities for HashSet<Entity, S> {
*self = self.drain().map(|e| entity_mapper.get_mapped(e)).collect();
}
}

impl<S: BuildHasher + Default> MapEntities for IndexSet<Entity, S> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
*self = self
.drain(..)
.map(|e| entity_mapper.get_mapped(e))
.collect();
}
}

impl MapEntities for EntityIndexSet {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
*self = self
.drain(..)
.map(|e| entity_mapper.get_mapped(e))
.collect();
}
}

impl MapEntities for BTreeSet<Entity> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
*self = mem::take(self)
.into_iter()
.map(|e| entity_mapper.get_mapped(e))
.collect();
}
}

impl MapEntities for Vec<Entity> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
for entity in self.iter_mut() {
Expand All @@ -95,6 +129,7 @@ impl<A: smallvec::Array<Item = Entity>> MapEntities for SmallVec<A> {
}
}
}

/// An implementor of this trait knows how to map an [`Entity`] into another [`Entity`].
///
/// Usually this is done by using an [`EntityHashMap<Entity>`] to map source entities
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub mod prelude {
Command, Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef,
IntoSystem, Local, NonSend, NonSendMut, ParamSet, Populated, Query, ReadOnlySystem,
Res, ResMut, Single, System, SystemIn, SystemInput, SystemParamBuilder,
SystemParamFunction,
SystemParamFunction, When,
},
world::{
EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
Expand Down
176 changes: 174 additions & 2 deletions crates/bevy_ecs/src/relationship/relationship_source_collection.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
use crate::entity::{hash_set::EntityHashSet, Entity};
use alloc::collections::{btree_set, BTreeSet};
use core::{
hash::BuildHasher,
ops::{Deref, DerefMut},
};

use crate::entity::{Entity, EntityHashSet, EntityIndexSet};
use alloc::vec::Vec;
use indexmap::IndexSet;
use smallvec::SmallVec;

/// The internal [`Entity`] collection used by a [`RelationshipTarget`](crate::relationship::RelationshipTarget) component.
Expand Down Expand Up @@ -445,6 +452,138 @@ impl<const N: usize> OrderedRelationshipSourceCollection for SmallVec<[Entity; N
}
}

impl<S: BuildHasher + Default> RelationshipSourceCollection for IndexSet<Entity, S> {
type SourceIter<'a>
= core::iter::Copied<indexmap::set::Iter<'a, Entity>>
where
S: 'a;

fn new() -> Self {
IndexSet::default()
}

fn reserve(&mut self, additional: usize) {
self.reserve(additional);
}

fn with_capacity(capacity: usize) -> Self {
IndexSet::with_capacity_and_hasher(capacity, S::default())
}

fn add(&mut self, entity: Entity) -> bool {
self.insert(entity)
}

fn remove(&mut self, entity: Entity) -> bool {
self.shift_remove(&entity)
}

fn iter(&self) -> Self::SourceIter<'_> {
self.iter().copied()
}

fn len(&self) -> usize {
self.len()
}

fn clear(&mut self) {
self.clear();
}

fn shrink_to_fit(&mut self) {
self.shrink_to_fit();
}

fn extend_from_iter(&mut self, entities: impl IntoIterator<Item = Entity>) {
self.extend(entities);
}
}

impl RelationshipSourceCollection for EntityIndexSet {
type SourceIter<'a> = core::iter::Copied<crate::entity::index_set::Iter<'a>>;

fn new() -> Self {
EntityIndexSet::new()
}

fn reserve(&mut self, additional: usize) {
self.deref_mut().reserve(additional);
}

fn with_capacity(capacity: usize) -> Self {
EntityIndexSet::with_capacity(capacity)
}

fn add(&mut self, entity: Entity) -> bool {
self.insert(entity)
}

fn remove(&mut self, entity: Entity) -> bool {
self.deref_mut().shift_remove(&entity)
}

fn iter(&self) -> Self::SourceIter<'_> {
self.iter().copied()
}

fn len(&self) -> usize {
self.deref().len()
}

fn clear(&mut self) {
self.deref_mut().clear();
}

fn shrink_to_fit(&mut self) {
self.deref_mut().shrink_to_fit();
}

fn extend_from_iter(&mut self, entities: impl IntoIterator<Item = Entity>) {
self.extend(entities);
}
}

impl RelationshipSourceCollection for BTreeSet<Entity> {
type SourceIter<'a> = core::iter::Copied<btree_set::Iter<'a, Entity>>;

fn new() -> Self {
BTreeSet::new()
}

fn with_capacity(_: usize) -> Self {
// BTreeSet doesn't have a capacity
Self::new()
}

fn reserve(&mut self, _: usize) {
// BTreeSet doesn't have a capacity
}

fn add(&mut self, entity: Entity) -> bool {
self.insert(entity)
}

fn remove(&mut self, entity: Entity) -> bool {
self.remove(&entity)
}

fn iter(&self) -> Self::SourceIter<'_> {
self.iter().copied()
}

fn len(&self) -> usize {
self.len()
}

fn clear(&mut self) {
self.clear();
}

fn shrink_to_fit(&mut self) {
// BTreeSet doesn't have a capacity
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -547,6 +686,40 @@ mod tests {
assert_eq!(a, world.get::<Below>(c).unwrap().0);
}

#[test]
fn entity_index_map() {
#[derive(Component)]
#[relationship(relationship_target = RelTarget)]
struct Rel(Entity);

#[derive(Component)]
#[relationship_target(relationship = Rel, linked_spawn)]
struct RelTarget(EntityHashSet);

let mut world = World::new();
let a = world.spawn_empty().id();
let b = world.spawn_empty().id();
let c = world.spawn_empty().id();

let d = world.spawn_empty().id();

world.entity_mut(a).add_related::<Rel>(&[b, c, d]);

let rel_target = world.get::<RelTarget>(a).unwrap();
let collection = rel_target.collection();

// Insertions should maintain ordering
assert!(collection.iter().eq(&[b, c, d]));

world.entity_mut(c).despawn();

let rel_target = world.get::<RelTarget>(a).unwrap();
let collection = rel_target.collection();

// Removals should maintain ordering
assert!(collection.iter().eq(&[b, d]));
}

#[test]
#[should_panic]
fn one_to_one_relationship_shared_target() {
Expand All @@ -557,7 +730,6 @@ mod tests {
#[derive(Component)]
#[relationship_target(relationship = Above)]
struct Below(Entity);

let mut world = World::new();
let a = world.spawn_empty().id();
let b = world.spawn_empty().id();
Expand Down
15 changes: 14 additions & 1 deletion crates/bevy_ecs/src/system/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
query::{QueryData, QueryFilter, QueryState},
resource::Resource,
system::{
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam,
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam, When,
},
world::{
FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut,
Expand Down Expand Up @@ -710,6 +710,19 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)>
}
}

/// A [`SystemParamBuilder`] for a [`When`].
#[derive(Clone)]
pub struct WhenBuilder<T>(T);

// SAFETY: `WhenBuilder<B>` builds a state that is valid for `P`, and any state valid for `P` is valid for `When<P>`
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<When<P>>
for WhenBuilder<B>
{
fn build(self, world: &mut World, meta: &mut SystemMeta) -> <When<P> as SystemParam>::State {
self.0.build(world, meta)
}
}

#[cfg(test)]
mod tests {
use crate::{
Expand Down
Loading
Loading